From eb24365786f4f84c512441c13b3e10095ba46a70 Mon Sep 17 00:00:00 2001 From: Matt Knight Date: Wed, 11 Jan 2023 21:31:21 -0800 Subject: [PATCH] regzon (#79) regzon is the name for the json output for reg'z data model. This PR includes a number of tests for serialization and deserialization. The intention of this format is to provide an accessible format for tooling, and to provide a medium for schema patching. Rather than errata, patching can be utilized to improve codegen such as type deduplication. This format is subject to change. --- tools/regz/src/Database.zig | 68 +- tools/regz/src/atdf.zig | 21 +- tools/regz/src/gen.zig | 395 +------- tools/regz/src/main.zig | 24 +- tools/regz/src/output_tests.zig | 487 ++++++++++ tools/regz/src/regzon.zig | 1489 ++++++++++++++++++++++++++++++- tools/regz/src/testing.zig | 197 ++++ 7 files changed, 2236 insertions(+), 445 deletions(-) create mode 100644 tools/regz/src/output_tests.zig diff --git a/tools/regz/src/Database.zig b/tools/regz/src/Database.zig index dfa9ef3..017a23d 100644 --- a/tools/regz/src/Database.zig +++ b/tools/regz/src/Database.zig @@ -26,6 +26,7 @@ attrs: struct { } = .{}, children: struct { + modes: ArrayHashMap(EntityId, EntitySet) = .{}, interrupts: ArrayHashMap(EntityId, EntitySet) = .{}, peripherals: ArrayHashMap(EntityId, EntitySet) = .{}, register_groups: ArrayHashMap(EntityId, EntitySet) = .{}, @@ -33,19 +34,18 @@ children: struct { fields: ArrayHashMap(EntityId, EntitySet) = .{}, enums: ArrayHashMap(EntityId, EntitySet) = .{}, enum_fields: ArrayHashMap(EntityId, EntitySet) = .{}, - modes: ArrayHashMap(EntityId, EntitySet) = .{}, } = .{}, types: struct { + // atdf has modes which make registers into unions + modes: ArrayHashMap(EntityId, Mode) = .{}, + peripherals: ArrayHashMap(EntityId, void) = .{}, register_groups: ArrayHashMap(EntityId, void) = .{}, registers: ArrayHashMap(EntityId, void) = .{}, fields: ArrayHashMap(EntityId, void) = .{}, enums: ArrayHashMap(EntityId, void) = .{}, enum_fields: ArrayHashMap(EntityId, u32) = .{}, - - // atdf has modes which make registers into unions - modes: ArrayHashMap(EntityId, Mode) = .{}, } = .{}, instances: struct { @@ -282,11 +282,11 @@ pub fn initFromDslite(allocator: Allocator, doc: xml.Doc) !Database { return db; } -pub fn initFromJson(allocator: Allocator, reader: anytype) !Database { +pub fn initFromJson(allocator: Allocator, text: []const u8) !Database { var db = try Database.init(allocator); errdefer db.deinit(); - try regzon.loadIntoDb(&db, reader); + try regzon.loadIntoDb(&db, text); return db; } @@ -333,6 +333,7 @@ pub fn createDevice( opts: struct { // required for now name: []const u8, + description: ?[]const u8 = null, arch: Arch = .unknown, }, ) !EntityId { @@ -345,6 +346,8 @@ pub fn createDevice( }); try db.addName(id, opts.name); + if (opts.description) |d| + try db.addDescription(id, d); return id; } @@ -356,6 +359,7 @@ pub fn createPeripheralInstance( opts: struct { // required for now name: []const u8, + description: ?[]const u8 = null, // required for now offset: u64, // count for an array @@ -374,6 +378,9 @@ pub fn createPeripheralInstance( try db.addName(id, opts.name); try db.addOffset(id, opts.offset); + if (opts.description) |d| + try db.addDescription(id, d); + if (opts.count) |c| try db.addCount(id, c); @@ -385,6 +392,7 @@ pub fn createPeripheral( db: *Database, opts: struct { name: []const u8, + description: ?[]const u8 = null, size: ?u64 = null, }, ) !EntityId { @@ -396,6 +404,9 @@ pub fn createPeripheral( try db.types.peripherals.put(db.gpa, id, {}); try db.addName(id, opts.name); + if (opts.description) |d| + try db.addDescription(id, d); + if (opts.size) |s| try db.addSize(id, s); @@ -407,6 +418,7 @@ pub fn createRegisterGroup( parent_id: EntityId, opts: struct { name: []const u8, + description: ?[]const u8 = null, }, ) !EntityId { assert(db.entityIs("type.peripheral", parent_id)); @@ -418,6 +430,9 @@ pub fn createRegisterGroup( try db.types.register_groups.put(db.gpa, id, {}); try db.addName(id, opts.name); + if (opts.description) |d| + try db.addDescription(id, d); + try db.addChild("type.register_group", parent_id, id); return id; } @@ -527,6 +542,7 @@ pub fn createEnum( parent_id: EntityId, opts: struct { name: ?[]const u8 = null, + description: ?[]const u8 = null, size: ?u64 = null, }, ) !EntityId { @@ -541,6 +557,9 @@ pub fn createEnum( if (opts.name) |n| try db.addName(id, n); + if (opts.description) |d| + try db.addDescription(id, d); + if (opts.size) |s| try db.addSize(id, s); @@ -573,6 +592,30 @@ pub fn createEnumField( return id; } +pub fn createMode(db: *Database, parent_id: EntityId, opts: struct { + name: []const u8, + description: ?[]const u8 = null, + value: []const u8, + qualifier: []const u8, +}) !EntityId { + // TODO: what types of parents can it have? + const id = db.createEntity(); + errdefer db.destroyEntity(id); + + log.debug("{}: creating mode", .{id}); + try db.types.modes.put(db.gpa, id, .{ + .value = try db.arena.allocator().dupe(u8, opts.value), + .qualifier = try db.arena.allocator().dupe(u8, opts.qualifier), + }); + try db.addName(id, opts.name); + + if (opts.description) |d| + try db.addDescription(id, d); + + try db.addChild("type.mode", parent_id, id); + return id; +} + pub fn createInterrupt(db: *Database, device_id: EntityId, opts: struct { name: []const u8, index: i32, @@ -701,7 +744,7 @@ pub fn addDeviceProperty( if (db.instances.devices.getEntry(id)) |entry| try entry.value_ptr.properties.put( db.gpa, - key, + try db.arena.allocator().dupe(u8, key), try db.arena.allocator().dupe(u8, value), ) else @@ -754,6 +797,17 @@ pub const EntityType = enum { device, interrupt, peripheral_instance, + + pub fn isInstance(entity_type: EntityType) bool { + return switch (entity_type) { + .device, .interrupt, .peripheral_instance => true, + else => false, + }; + } + + pub fn isType(entity_type: EntityType) bool { + return !entity_type.isType(); + } }; pub fn getEntityType( diff --git a/tools/regz/src/atdf.zig b/tools/regz/src/atdf.zig index c9b296e..a2a4dc8 100644 --- a/tools/regz/src/atdf.zig +++ b/tools/regz/src/atdf.zig @@ -468,23 +468,14 @@ fn loadMode(ctx: *Context, node: xml.Node, parent_id: EntityId) !void { "caption", }); - const id = db.createEntity(); - errdefer db.destroyEntity(id); - - const value_str = node.getAttribute("value") orelse return error.MissingModeValue; - const qualifier = node.getAttribute("qualifier") orelse return error.MissingModeQualifier; - log.debug("{}: creating mode, value={s}, qualifier={s}", .{ id, value_str, qualifier }); - try db.types.modes.put(db.gpa, id, .{ - .value = try db.arena.allocator().dupe(u8, value_str), - .qualifier = try db.arena.allocator().dupe(u8, qualifier), + const id = try db.createMode(parent_id, .{ + .name = node.getAttribute("name") orelse return error.MissingModeName, + .description = node.getAttribute("caption"), + .value = node.getAttribute("value") orelse return error.MissingModeValue, + .qualifier = node.getAttribute("qualifier") orelse return error.MissingModeQualifier, }); + errdefer db.destroyEntity(id); - const name = node.getAttribute("name") orelse return error.MissingModeName; - try db.addName(id, name); - if (node.getAttribute("caption")) |caption| - try db.addDescription(id, caption); - - try db.addChild("type.mode", parent_id, id); // TODO: "mask": "optional", } diff --git a/tools/regz/src/gen.zig b/tools/regz/src/gen.zig index 43c7011..981b55e 100644 --- a/tools/regz/src/gen.zig +++ b/tools/regz/src/gen.zig @@ -877,26 +877,12 @@ fn getOrderedRegisterList( return registers; } +const tests = @import("output_tests.zig"); + test "gen.peripheral type with register and field" { - var db = try Database.init(std.testing.allocator); + var db = try tests.peripheralTypeWithRegisterAndField(std.testing.allocator); defer db.deinit(); - const peripheral_id = try db.createPeripheral(.{ - .name = "TEST_PERIPHERAL", - }); - - const register_id = try db.createRegister(peripheral_id, .{ - .name = "TEST_REGISTER", - .size = 32, - .offset = 0, - }); - - _ = try db.createField(register_id, .{ - .name = "TEST_FIELD", - .size = 1, - .offset = 0, - }); - var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); @@ -918,34 +904,9 @@ test "gen.peripheral type with register and field" { } test "gen.peripheral instantiation" { - var db = try Database.init(std.testing.allocator); + var db = try tests.peripheralInstantiation(std.testing.allocator); defer db.deinit(); - const peripheral_id = try db.createPeripheral(.{ - .name = "TEST_PERIPHERAL", - }); - - const register_id = try db.createRegister(peripheral_id, .{ - .name = "TEST_REGISTER", - .size = 32, - .offset = 0, - }); - - _ = try db.createField(register_id, .{ - .name = "TEST_FIELD", - .size = 1, - .offset = 0, - }); - - const device_id = try db.createDevice(.{ - .name = "TEST_DEVICE", - }); - - _ = try db.createPeripheralInstance(device_id, peripheral_id, .{ - .name = "TEST0", - .offset = 0x1000, - }); - var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); @@ -975,32 +936,9 @@ test "gen.peripheral instantiation" { } test "gen.peripherals with a shared type" { - var db = try Database.init(std.testing.allocator); + var db = try tests.peripheralsWithSharedType(std.testing.allocator); defer db.deinit(); - const peripheral_id = try db.createPeripheral(.{ - .name = "TEST_PERIPHERAL", - }); - - const register_id = try db.createRegister(peripheral_id, .{ - .name = "TEST_REGISTER", - .size = 32, - .offset = 0, - }); - - _ = try db.createField(register_id, .{ - .name = "TEST_FIELD", - .size = 1, - .offset = 0, - }); - - const device_id = try db.createDevice(.{ - .name = "TEST_DEVICE", - }); - - _ = try db.createPeripheralInstance(device_id, peripheral_id, .{ .name = "TEST0", .offset = 0x1000 }); - _ = try db.createPeripheralInstance(device_id, peripheral_id, .{ .name = "TEST1", .offset = 0x2000 }); - var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); @@ -1031,54 +969,9 @@ test "gen.peripherals with a shared type" { } test "gen.peripheral with modes" { - var db = try Database.init(std.testing.allocator); + var db = try tests.peripheralWithModes(std.testing.allocator); defer db.deinit(); - const mode1_id = db.createEntity(); - try db.addName(mode1_id, "TEST_MODE1"); - try db.types.modes.put(db.gpa, mode1_id, .{ - .value = "0x00", - .qualifier = "TEST_PERIPHERAL.TEST_MODE1.COMMON_REGISTER.TEST_FIELD", - }); - - const mode2_id = db.createEntity(); - try db.addName(mode2_id, "TEST_MODE2"); - try db.types.modes.put(db.gpa, mode2_id, .{ - .value = "0x01", - .qualifier = "TEST_PERIPHERAL.TEST_MODE2.COMMON_REGISTER.TEST_FIELD", - }); - - var register1_modeset = EntitySet{}; - try register1_modeset.put(db.gpa, mode1_id, {}); - - var register2_modeset = EntitySet{}; - try register2_modeset.put(db.gpa, mode2_id, {}); - - const peripheral_id = try db.createPeripheral(.{ .name = "TEST_PERIPHERAL" }); - try db.addChild("type.mode", peripheral_id, mode1_id); - try db.addChild("type.mode", peripheral_id, mode2_id); - - const register1_id = try db.createRegister(peripheral_id, .{ .name = "TEST_REGISTER1", .size = 32, .offset = 0 }); - const register2_id = try db.createRegister(peripheral_id, .{ .name = "TEST_REGISTER2", .size = 32, .offset = 0 }); - const common_reg_id = try db.createRegister(peripheral_id, .{ .name = "COMMON_REGISTER", .size = 32, .offset = 4 }); - - try db.attrs.modes.put(db.gpa, register1_id, register1_modeset); - try db.attrs.modes.put(db.gpa, register2_id, register2_modeset); - - _ = try db.createField(common_reg_id, .{ - .name = "TEST_FIELD", - .size = 1, - .offset = 0, - }); - - // TODO: study the types of qualifiers that come up. it's possible that - // we'll have to read different registers or read registers without fields. - // - // might also have registers with enum values - // naive implementation goes through each mode and follows the qualifier, - // next level will determine if they're reading the same address even if - // different modes will use different union members - var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); @@ -1134,27 +1027,9 @@ test "gen.peripheral with modes" { } test "gen.peripheral with enum" { - var db = try Database.init(std.testing.allocator); + var db = try tests.peripheralWithEnum(std.testing.allocator); defer db.deinit(); - const peripheral_id = try db.createPeripheral(.{ - .name = "TEST_PERIPHERAL", - }); - - const enum_id = try db.createEnum(peripheral_id, .{ - .name = "TEST_ENUM", - .size = 4, - }); - - _ = try db.createEnumField(enum_id, .{ .name = "TEST_ENUM_FIELD1", .value = 0 }); - _ = try db.createEnumField(enum_id, .{ .name = "TEST_ENUM_FIELD2", .value = 1 }); - - _ = try db.createRegister(peripheral_id, .{ - .name = "TEST_REGISTER", - .size = 8, - .offset = 0, - }); - var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); @@ -1179,27 +1054,9 @@ test "gen.peripheral with enum" { } test "gen.peripheral with enum, enum is exhausted of values" { - var db = try Database.init(std.testing.allocator); + var db = try tests.peripheralWithEnumEnumIsExhaustedOfValues(std.testing.allocator); defer db.deinit(); - const peripheral_id = try db.createPeripheral(.{ - .name = "TEST_PERIPHERAL", - }); - - const enum_id = try db.createEnum(peripheral_id, .{ - .name = "TEST_ENUM", - .size = 1, - }); - - _ = try db.createEnumField(enum_id, .{ .name = "TEST_ENUM_FIELD1", .value = 0 }); - _ = try db.createEnumField(enum_id, .{ .name = "TEST_ENUM_FIELD2", .value = 1 }); - - _ = try db.createRegister(peripheral_id, .{ - .name = "TEST_REGISTER", - .size = 8, - .offset = 0, - }); - var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); @@ -1223,34 +1080,9 @@ test "gen.peripheral with enum, enum is exhausted of values" { } test "gen.field with named enum" { - var db = try Database.init(std.testing.allocator); + var db = try tests.fieldWithNamedEnum(std.testing.allocator); defer db.deinit(); - const peripheral_id = try db.createPeripheral(.{ - .name = "TEST_PERIPHERAL", - }); - - const enum_id = try db.createEnum(peripheral_id, .{ - .name = "TEST_ENUM", - .size = 4, - }); - - _ = try db.createEnumField(enum_id, .{ .name = "TEST_ENUM_FIELD1", .value = 0 }); - _ = try db.createEnumField(enum_id, .{ .name = "TEST_ENUM_FIELD2", .value = 1 }); - - const register_id = try db.createRegister(peripheral_id, .{ - .name = "TEST_REGISTER", - .size = 8, - .offset = 0, - }); - - _ = try db.createField(register_id, .{ - .name = "TEST_FIELD", - .size = 4, - .offset = 0, - .enum_id = enum_id, - }); - var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); @@ -1281,33 +1113,9 @@ test "gen.field with named enum" { } test "gen.field with anonymous enum" { - var db = try Database.init(std.testing.allocator); + var db = try tests.fieldWithAnonymousEnum(std.testing.allocator); defer db.deinit(); - const peripheral_id = try db.createPeripheral(.{ - .name = "TEST_PERIPHERAL", - }); - - const enum_id = try db.createEnum(peripheral_id, .{ - .size = 4, - }); - - _ = try db.createEnumField(enum_id, .{ .name = "TEST_ENUM_FIELD1", .value = 0 }); - _ = try db.createEnumField(enum_id, .{ .name = "TEST_ENUM_FIELD2", .value = 1 }); - - const register_id = try db.createRegister(peripheral_id, .{ - .name = "TEST_REGISTER", - .size = 8, - .offset = 0, - }); - - _ = try db.createField(register_id, .{ - .name = "TEST_FIELD", - .size = 4, - .offset = 0, - .enum_id = enum_id, - }); - var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); @@ -1336,33 +1144,9 @@ test "gen.field with anonymous enum" { } test "gen.namespaced register groups" { - var db = try Database.init(std.testing.allocator); + var db = try tests.namespacedRegisterGroups(std.testing.allocator); defer db.deinit(); - // peripheral - const peripheral_id = try db.createPeripheral(.{ - .name = "PORT", - }); - - // register_groups - const portb_group_id = try db.createRegisterGroup(peripheral_id, .{ .name = "PORTB" }); - const portc_group_id = try db.createRegisterGroup(peripheral_id, .{ .name = "PORTC" }); - - // registers - _ = try db.createRegister(portb_group_id, .{ .name = "PORTB", .size = 8, .offset = 0 }); - _ = try db.createRegister(portb_group_id, .{ .name = "DDRB", .size = 8, .offset = 1 }); - _ = try db.createRegister(portb_group_id, .{ .name = "PINB", .size = 8, .offset = 2 }); - _ = try db.createRegister(portc_group_id, .{ .name = "PORTC", .size = 8, .offset = 0 }); - _ = try db.createRegister(portc_group_id, .{ .name = "DDRC", .size = 8, .offset = 1 }); - _ = try db.createRegister(portc_group_id, .{ .name = "PINC", .size = 8, .offset = 2 }); - - // device - const device_id = try db.createDevice(.{ .name = "ATmega328P" }); - - // instances - _ = try db.createPeripheralInstance(device_id, portb_group_id, .{ .name = "PORTB", .offset = 0x23 }); - _ = try db.createPeripheralInstance(device_id, portc_group_id, .{ .name = "PORTC", .offset = 0x26 }); - var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); @@ -1400,25 +1184,9 @@ test "gen.namespaced register groups" { } test "gen.peripheral with reserved register" { - var db = try Database.init(std.testing.allocator); + var db = try tests.peripheralWithReservedRegister(std.testing.allocator); defer db.deinit(); - const peripheral_id = try db.createPeripheral(.{ - .name = "PORTB", - }); - - _ = try db.createRegister(peripheral_id, .{ .name = "PORTB", .size = 32, .offset = 0 }); - _ = try db.createRegister(peripheral_id, .{ .name = "PINB", .size = 32, .offset = 8 }); - - const device_id = try db.createDevice(.{ - .name = "ATmega328P", - }); - - _ = try db.createPeripheralInstance(device_id, peripheral_id, .{ - .name = "PORTB", - .offset = 0x23, - }); - var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); @@ -1447,26 +1215,9 @@ test "gen.peripheral with reserved register" { } test "gen.peripheral with count" { - var db = try Database.init(std.testing.allocator); + var db = try tests.peripheralWithCount(std.testing.allocator); defer db.deinit(); - const device_id = try db.createDevice(.{ .name = "ATmega328P" }); - - const peripheral_id = try db.createPeripheral(.{ - .name = "PORTB", - .size = 3, - }); - - _ = try db.createPeripheralInstance(device_id, peripheral_id, .{ - .name = "PORTB", - .offset = 0x23, - .count = 4, - }); - - _ = try db.createRegister(peripheral_id, .{ .name = "PORTB", .size = 8, .offset = 0 }); - _ = try db.createRegister(peripheral_id, .{ .name = "DDRB", .size = 8, .offset = 1 }); - _ = try db.createRegister(peripheral_id, .{ .name = "PINB", .size = 8, .offset = 2 }); - var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); @@ -1495,26 +1246,9 @@ test "gen.peripheral with count" { } test "gen.peripheral with count, padding required" { - var db = try Database.init(std.testing.allocator); + var db = try tests.peripheralWithCountPaddingRequired(std.testing.allocator); defer db.deinit(); - const device_id = try db.createDevice(.{ .name = "ATmega328P" }); - - const peripheral_id = try db.createPeripheral(.{ - .name = "PORTB", - .size = 4, - }); - - _ = try db.createPeripheralInstance(device_id, peripheral_id, .{ - .name = "PORTB", - .offset = 0x23, - .count = 4, - }); - - _ = try db.createRegister(peripheral_id, .{ .name = "PORTB", .size = 8, .offset = 0 }); - _ = try db.createRegister(peripheral_id, .{ .name = "DDRB", .size = 8, .offset = 1 }); - _ = try db.createRegister(peripheral_id, .{ .name = "PINB", .size = 8, .offset = 2 }); - var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); @@ -1544,24 +1278,9 @@ test "gen.peripheral with count, padding required" { } test "gen.register with count" { - var db = try Database.init(std.testing.allocator); + var db = try tests.registerWithCount(std.testing.allocator); defer db.deinit(); - const device_id = try db.createDevice(.{ .name = "ATmega328P" }); - - const peripheral_id = try db.createPeripheral(.{ - .name = "PORTB", - }); - - _ = try db.createPeripheralInstance(device_id, peripheral_id, .{ - .name = "PORTB", - .offset = 0x23, - }); - - _ = try db.createRegister(peripheral_id, .{ .name = "PORTB", .size = 8, .offset = 0, .count = 4 }); - _ = try db.createRegister(peripheral_id, .{ .name = "DDRB", .size = 8, .offset = 4 }); - _ = try db.createRegister(peripheral_id, .{ .name = "PINB", .size = 8, .offset = 5 }); - var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); @@ -1590,36 +1309,9 @@ test "gen.register with count" { } test "gen.register with count and fields" { - var db = try Database.init(std.testing.allocator); + var db = try tests.registerWithCountAndFields(std.testing.allocator); defer db.deinit(); - const device_id = try db.createDevice(.{ .name = "ATmega328P" }); - - const peripheral_id = try db.createPeripheral(.{ - .name = "PORTB", - }); - - _ = try db.createPeripheralInstance(device_id, peripheral_id, .{ - .name = "PORTB", - .offset = 0x23, - }); - - const portb_id = try db.createRegister(peripheral_id, .{ - .name = "PORTB", - .size = 8, - .offset = 0, - .count = 4, - }); - - _ = try db.createRegister(peripheral_id, .{ .name = "DDRB", .size = 8, .offset = 4 }); - _ = try db.createRegister(peripheral_id, .{ .name = "PINB", .size = 8, .offset = 5 }); - - _ = try db.createField(portb_id, .{ - .name = "TEST_FIELD", - .size = 4, - .offset = 0, - }); - var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); @@ -1651,26 +1343,9 @@ test "gen.register with count and fields" { } test "gen.field with count, width of one, offset, and padding" { - var db = try Database.init(std.testing.allocator); + var db = try tests.fieldWithCountWidthOfOneOffsetAndPadding(std.testing.allocator); defer db.deinit(); - const peripheral_id = try db.createPeripheral(.{ - .name = "PORTB", - }); - - const portb_id = try db.createRegister(peripheral_id, .{ - .name = "PORTB", - .size = 8, - .offset = 0, - }); - - _ = try db.createField(portb_id, .{ - .name = "TEST_FIELD", - .size = 1, - .offset = 2, - .count = 5, - }); - var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); @@ -1693,26 +1368,9 @@ test "gen.field with count, width of one, offset, and padding" { } test "gen.field with count, multi-bit width, offset, and padding" { - var db = try Database.init(std.testing.allocator); + var db = try tests.fieldWithCountMultiBitWidthOffsetAndPadding(std.testing.allocator); defer db.deinit(); - const peripheral_id = try db.createPeripheral(.{ - .name = "PORTB", - }); - - const portb_id = try db.createRegister(peripheral_id, .{ - .name = "PORTB", - .size = 8, - .offset = 0, - }); - - _ = try db.createField(portb_id, .{ - .name = "TEST_FIELD", - .size = 2, - .offset = 2, - .count = 2, - }); - var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); @@ -1735,24 +1393,9 @@ test "gen.field with count, multi-bit width, offset, and padding" { } test "gen.interrupts.avr" { - var db = try Database.init(std.testing.allocator); + var db = try tests.interruptsAvr(std.testing.allocator); defer db.deinit(); - const device_id = try db.createDevice(.{ - .name = "ATmega328P", - .arch = .avr8, - }); - - _ = try db.createInterrupt(device_id, .{ - .name = "TEST_VECTOR1", - .index = 1, - }); - - _ = try db.createInterrupt(device_id, .{ - .name = "TEST_VECTOR2", - .index = 3, - }); - var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); diff --git a/tools/regz/src/main.zig b/tools/regz/src/main.zig index 68fb6c6..88223a6 100644 --- a/tools/regz/src/main.zig +++ b/tools/regz/src/main.zig @@ -61,12 +61,9 @@ fn mainImpl() anyerror!void { var schema: ?Schema = if (res.args.schema) |schema_str| if (std.meta.stringToEnum(Schema, schema_str)) |s| s else { - std.log.err( - "Unknown schema type: {s}, must be one of: svd, atdf, json", - .{ - schema_str, - }, - ); + std.log.err("Unknown schema type: {s}, must be one of: svd, atdf, json", .{ + schema_str, + }); return error.Explained; } else @@ -79,10 +76,6 @@ fn mainImpl() anyerror!void { return error.Explained; } - if (schema.? == .json) { - return error.Todo; - } - var stdin = std.io.getStdIn().reader(); var doc = try xml.Doc.fromIo(readFn, &stdin); defer doc.deinit(); @@ -101,13 +94,18 @@ fn mainImpl() anyerror!void { } } - // schema is guaranteed to be non-null from this point on + const path = try arena.allocator().dupeZ(u8, res.positionals[0]); if (schema.? == .json) { - return error.Todo; + const file = try std.fs.cwd().openFile(path, .{}); + defer file.close(); + + const text = try file.reader().readAllAlloc(allocator, std.math.maxInt(usize)); + defer allocator.free(text); + + break :blk try Database.initFromJson(allocator, text); } // all other schema types are xml based - const path = try arena.allocator().dupeZ(u8, res.positionals[0]); var doc = try xml.Doc.fromFile(path); defer doc.deinit(); diff --git a/tools/regz/src/output_tests.zig b/tools/regz/src/output_tests.zig new file mode 100644 index 0000000..4fb8fc0 --- /dev/null +++ b/tools/regz/src/output_tests.zig @@ -0,0 +1,487 @@ +//! Common test conditions for code generation and regzon +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const Database = @import("Database.zig"); +const EntitySet = Database.EntitySet; + +pub fn peripheralTypeWithRegisterAndField(allocator: Allocator) !Database { + var db = try Database.init(allocator); + errdefer db.deinit(); + + const peripheral_id = try db.createPeripheral(.{ + .name = "TEST_PERIPHERAL", + //.description = "test peripheral", + }); + + const register_id = try db.createRegister(peripheral_id, .{ + .name = "TEST_REGISTER", + //.description = "test register", + .size = 32, + .offset = 0, + }); + + _ = try db.createField(register_id, .{ + .name = "TEST_FIELD", + //.description = "test field", + .size = 1, + .offset = 0, + }); + + return db; +} + +pub fn peripheralInstantiation(allocator: Allocator) !Database { + var db = try Database.init(allocator); + errdefer db.deinit(); + + const peripheral_id = try db.createPeripheral(.{ + .name = "TEST_PERIPHERAL", + }); + + const register_id = try db.createRegister(peripheral_id, .{ + .name = "TEST_REGISTER", + .size = 32, + .offset = 0, + }); + + _ = try db.createField(register_id, .{ + .name = "TEST_FIELD", + .size = 1, + .offset = 0, + }); + + const device_id = try db.createDevice(.{ + .name = "TEST_DEVICE", + }); + + _ = try db.createPeripheralInstance(device_id, peripheral_id, .{ + .name = "TEST0", + .offset = 0x1000, + }); + + return db; +} + +pub fn peripheralsWithSharedType(allocator: Allocator) !Database { + var db = try Database.init(allocator); + errdefer db.deinit(); + + const peripheral_id = try db.createPeripheral(.{ + .name = "TEST_PERIPHERAL", + }); + + const register_id = try db.createRegister(peripheral_id, .{ + .name = "TEST_REGISTER", + .size = 32, + .offset = 0, + }); + + _ = try db.createField(register_id, .{ + .name = "TEST_FIELD", + .size = 1, + .offset = 0, + }); + + const device_id = try db.createDevice(.{ + .name = "TEST_DEVICE", + }); + + _ = try db.createPeripheralInstance(device_id, peripheral_id, .{ .name = "TEST0", .offset = 0x1000 }); + _ = try db.createPeripheralInstance(device_id, peripheral_id, .{ .name = "TEST1", .offset = 0x2000 }); + + return db; +} + +pub fn peripheralWithModes(allocator: Allocator) !Database { + var db = try Database.init(allocator); + errdefer db.deinit(); + + const mode1_id = db.createEntity(); + try db.addName(mode1_id, "TEST_MODE1"); + try db.types.modes.put(db.gpa, mode1_id, .{ + .value = "0x00", + .qualifier = "TEST_PERIPHERAL.TEST_MODE1.COMMON_REGISTER.TEST_FIELD", + }); + + const mode2_id = db.createEntity(); + try db.addName(mode2_id, "TEST_MODE2"); + try db.types.modes.put(db.gpa, mode2_id, .{ + .value = "0x01", + .qualifier = "TEST_PERIPHERAL.TEST_MODE2.COMMON_REGISTER.TEST_FIELD", + }); + + var register1_modeset = EntitySet{}; + try register1_modeset.put(db.gpa, mode1_id, {}); + + var register2_modeset = EntitySet{}; + try register2_modeset.put(db.gpa, mode2_id, {}); + + const peripheral_id = try db.createPeripheral(.{ .name = "TEST_PERIPHERAL" }); + try db.addChild("type.mode", peripheral_id, mode1_id); + try db.addChild("type.mode", peripheral_id, mode2_id); + + const register1_id = try db.createRegister(peripheral_id, .{ .name = "TEST_REGISTER1", .size = 32, .offset = 0 }); + const register2_id = try db.createRegister(peripheral_id, .{ .name = "TEST_REGISTER2", .size = 32, .offset = 0 }); + const common_reg_id = try db.createRegister(peripheral_id, .{ .name = "COMMON_REGISTER", .size = 32, .offset = 4 }); + + try db.attrs.modes.put(db.gpa, register1_id, register1_modeset); + try db.attrs.modes.put(db.gpa, register2_id, register2_modeset); + + _ = try db.createField(common_reg_id, .{ + .name = "TEST_FIELD", + .size = 1, + .offset = 0, + }); + + // TODO: study the types of qualifiers that come up. it's possible that + // we'll have to read different registers or read registers without fields. + // + // might also have registers with enum values + // naive implementation goes through each mode and follows the qualifier, + // next level will determine if they're reading the same address even if + // different modes will use different union members + + return db; +} + +pub fn peripheralWithEnum(allocator: Allocator) !Database { + var db = try Database.init(allocator); + errdefer db.deinit(); + + const peripheral_id = try db.createPeripheral(.{ + .name = "TEST_PERIPHERAL", + }); + + const enum_id = try db.createEnum(peripheral_id, .{ + .name = "TEST_ENUM", + .size = 4, + }); + + _ = try db.createEnumField(enum_id, .{ .name = "TEST_ENUM_FIELD1", .value = 0 }); + _ = try db.createEnumField(enum_id, .{ .name = "TEST_ENUM_FIELD2", .value = 1 }); + + _ = try db.createRegister(peripheral_id, .{ + .name = "TEST_REGISTER", + .size = 8, + .offset = 0, + }); + + return db; +} + +pub fn peripheralWithEnumEnumIsExhaustedOfValues(allocator: Allocator) !Database { + var db = try Database.init(allocator); + errdefer db.deinit(); + + const peripheral_id = try db.createPeripheral(.{ + .name = "TEST_PERIPHERAL", + }); + + const enum_id = try db.createEnum(peripheral_id, .{ + .name = "TEST_ENUM", + .size = 1, + }); + + _ = try db.createEnumField(enum_id, .{ .name = "TEST_ENUM_FIELD1", .value = 0 }); + _ = try db.createEnumField(enum_id, .{ .name = "TEST_ENUM_FIELD2", .value = 1 }); + + _ = try db.createRegister(peripheral_id, .{ + .name = "TEST_REGISTER", + .size = 8, + .offset = 0, + }); + + return db; +} + +pub fn fieldWithNamedEnum(allocator: Allocator) !Database { + var db = try Database.init(allocator); + errdefer db.deinit(); + + const peripheral_id = try db.createPeripheral(.{ + .name = "TEST_PERIPHERAL", + }); + + const enum_id = try db.createEnum(peripheral_id, .{ + .name = "TEST_ENUM", + .size = 4, + }); + + _ = try db.createEnumField(enum_id, .{ .name = "TEST_ENUM_FIELD1", .value = 0 }); + _ = try db.createEnumField(enum_id, .{ .name = "TEST_ENUM_FIELD2", .value = 1 }); + + const register_id = try db.createRegister(peripheral_id, .{ + .name = "TEST_REGISTER", + .size = 8, + .offset = 0, + }); + + _ = try db.createField(register_id, .{ + .name = "TEST_FIELD", + .size = 4, + .offset = 0, + .enum_id = enum_id, + }); + + return db; +} + +pub fn fieldWithAnonymousEnum(allocator: Allocator) !Database { + var db = try Database.init(allocator); + errdefer db.deinit(); + + const peripheral_id = try db.createPeripheral(.{ + .name = "TEST_PERIPHERAL", + }); + + const enum_id = try db.createEnum(peripheral_id, .{ + .size = 4, + }); + + _ = try db.createEnumField(enum_id, .{ .name = "TEST_ENUM_FIELD1", .value = 0 }); + _ = try db.createEnumField(enum_id, .{ .name = "TEST_ENUM_FIELD2", .value = 1 }); + + const register_id = try db.createRegister(peripheral_id, .{ + .name = "TEST_REGISTER", + .size = 8, + .offset = 0, + }); + + _ = try db.createField(register_id, .{ + .name = "TEST_FIELD", + .size = 4, + .offset = 0, + .enum_id = enum_id, + }); + + return db; +} + +pub fn namespacedRegisterGroups(allocator: Allocator) !Database { + var db = try Database.init(allocator); + errdefer db.deinit(); + + // peripheral + const peripheral_id = try db.createPeripheral(.{ + .name = "PORT", + }); + + // register_groups + const portb_group_id = try db.createRegisterGroup(peripheral_id, .{ .name = "PORTB" }); + const portc_group_id = try db.createRegisterGroup(peripheral_id, .{ .name = "PORTC" }); + + // registers + _ = try db.createRegister(portb_group_id, .{ .name = "PORTB", .size = 8, .offset = 0 }); + _ = try db.createRegister(portb_group_id, .{ .name = "DDRB", .size = 8, .offset = 1 }); + _ = try db.createRegister(portb_group_id, .{ .name = "PINB", .size = 8, .offset = 2 }); + _ = try db.createRegister(portc_group_id, .{ .name = "PORTC", .size = 8, .offset = 0 }); + _ = try db.createRegister(portc_group_id, .{ .name = "DDRC", .size = 8, .offset = 1 }); + _ = try db.createRegister(portc_group_id, .{ .name = "PINC", .size = 8, .offset = 2 }); + + // device + const device_id = try db.createDevice(.{ .name = "ATmega328P" }); + + // instances + _ = try db.createPeripheralInstance(device_id, portb_group_id, .{ .name = "PORTB", .offset = 0x23 }); + _ = try db.createPeripheralInstance(device_id, portc_group_id, .{ .name = "PORTC", .offset = 0x26 }); + + return db; +} + +pub fn peripheralWithReservedRegister(allocator: Allocator) !Database { + var db = try Database.init(allocator); + errdefer db.deinit(); + + const peripheral_id = try db.createPeripheral(.{ + .name = "PORTB", + }); + + _ = try db.createRegister(peripheral_id, .{ .name = "PORTB", .size = 32, .offset = 0 }); + _ = try db.createRegister(peripheral_id, .{ .name = "PINB", .size = 32, .offset = 8 }); + + const device_id = try db.createDevice(.{ + .name = "ATmega328P", + }); + + _ = try db.createPeripheralInstance(device_id, peripheral_id, .{ + .name = "PORTB", + .offset = 0x23, + }); + + return db; +} + +pub fn peripheralWithCount(allocator: Allocator) !Database { + var db = try Database.init(allocator); + errdefer db.deinit(); + + const device_id = try db.createDevice(.{ .name = "ATmega328P" }); + + const peripheral_id = try db.createPeripheral(.{ + .name = "PORTB", + .size = 3, + }); + + _ = try db.createPeripheralInstance(device_id, peripheral_id, .{ + .name = "PORTB", + .offset = 0x23, + .count = 4, + }); + + _ = try db.createRegister(peripheral_id, .{ .name = "PORTB", .size = 8, .offset = 0 }); + _ = try db.createRegister(peripheral_id, .{ .name = "DDRB", .size = 8, .offset = 1 }); + _ = try db.createRegister(peripheral_id, .{ .name = "PINB", .size = 8, .offset = 2 }); + + return db; +} + +pub fn peripheralWithCountPaddingRequired(allocator: Allocator) !Database { + var db = try Database.init(allocator); + errdefer db.deinit(); + + const device_id = try db.createDevice(.{ .name = "ATmega328P" }); + + const peripheral_id = try db.createPeripheral(.{ + .name = "PORTB", + .size = 4, + }); + + _ = try db.createPeripheralInstance(device_id, peripheral_id, .{ + .name = "PORTB", + .offset = 0x23, + .count = 4, + }); + + _ = try db.createRegister(peripheral_id, .{ .name = "PORTB", .size = 8, .offset = 0 }); + _ = try db.createRegister(peripheral_id, .{ .name = "DDRB", .size = 8, .offset = 1 }); + _ = try db.createRegister(peripheral_id, .{ .name = "PINB", .size = 8, .offset = 2 }); + + return db; +} + +pub fn registerWithCount(allocator: Allocator) !Database { + var db = try Database.init(allocator); + errdefer db.deinit(); + + const device_id = try db.createDevice(.{ .name = "ATmega328P" }); + + const peripheral_id = try db.createPeripheral(.{ + .name = "PORTB", + }); + + _ = try db.createPeripheralInstance(device_id, peripheral_id, .{ + .name = "PORTB", + .offset = 0x23, + }); + + _ = try db.createRegister(peripheral_id, .{ .name = "PORTB", .size = 8, .offset = 0, .count = 4 }); + _ = try db.createRegister(peripheral_id, .{ .name = "DDRB", .size = 8, .offset = 4 }); + _ = try db.createRegister(peripheral_id, .{ .name = "PINB", .size = 8, .offset = 5 }); + + return db; +} + +pub fn registerWithCountAndFields(allocator: Allocator) !Database { + var db = try Database.init(allocator); + errdefer db.deinit(); + + const device_id = try db.createDevice(.{ .name = "ATmega328P" }); + + const peripheral_id = try db.createPeripheral(.{ + .name = "PORTB", + }); + + _ = try db.createPeripheralInstance(device_id, peripheral_id, .{ + .name = "PORTB", + .offset = 0x23, + }); + + const portb_id = try db.createRegister(peripheral_id, .{ + .name = "PORTB", + .size = 8, + .offset = 0, + .count = 4, + }); + + _ = try db.createRegister(peripheral_id, .{ .name = "DDRB", .size = 8, .offset = 4 }); + _ = try db.createRegister(peripheral_id, .{ .name = "PINB", .size = 8, .offset = 5 }); + + _ = try db.createField(portb_id, .{ + .name = "TEST_FIELD", + .size = 4, + .offset = 0, + }); + + return db; +} + +pub fn fieldWithCountWidthOfOneOffsetAndPadding(allocator: Allocator) !Database { + var db = try Database.init(allocator); + errdefer db.deinit(); + + const peripheral_id = try db.createPeripheral(.{ + .name = "PORTB", + }); + + const portb_id = try db.createRegister(peripheral_id, .{ + .name = "PORTB", + .size = 8, + .offset = 0, + }); + + _ = try db.createField(portb_id, .{ + .name = "TEST_FIELD", + .size = 1, + .offset = 2, + .count = 5, + }); + + return db; +} + +pub fn fieldWithCountMultiBitWidthOffsetAndPadding(allocator: Allocator) !Database { + var db = try Database.init(allocator); + errdefer db.deinit(); + + const peripheral_id = try db.createPeripheral(.{ + .name = "PORTB", + }); + + const portb_id = try db.createRegister(peripheral_id, .{ + .name = "PORTB", + .size = 8, + .offset = 0, + }); + + _ = try db.createField(portb_id, .{ + .name = "TEST_FIELD", + .size = 2, + .offset = 2, + .count = 2, + }); + + return db; +} + +pub fn interruptsAvr(allocator: Allocator) !Database { + var db = try Database.init(allocator); + errdefer db.deinit(); + + const device_id = try db.createDevice(.{ + .name = "ATmega328P", + .arch = .avr8, + }); + + _ = try db.createInterrupt(device_id, .{ + .name = "TEST_VECTOR1", + .index = 1, + }); + + _ = try db.createInterrupt(device_id, .{ + .name = "TEST_VECTOR2", + .index = 3, + }); + + return db; +} diff --git a/tools/regz/src/regzon.zig b/tools/regz/src/regzon.zig index c90b7cb..ac93128 100644 --- a/tools/regz/src/regzon.zig +++ b/tools/regz/src/regzon.zig @@ -3,17 +3,602 @@ const std = @import("std"); const json = std.json; const assert = std.debug.assert; const ArenaAllocator = std.heap.ArenaAllocator; +const Allocator = std.mem.Allocator; const Database = @import("Database.zig"); const EntityId = Database.EntityId; const PeripheralInstance = Database.PeripheralInstance; +const testing = @import("testing.zig"); +const TypeOfField = testing.TypeOfField; + const log = std.log.scoped(.regzon); -pub fn loadIntoDb(db: *Database, reader: anytype) !void { - _ = db; - _ = reader; - return error.Todo; +pub const schema_version = "0.1.0"; + +const LoadContext = struct { + db: *Database, + arena: std.heap.ArenaAllocator, + enum_refs: std.AutoArrayHashMapUnmanaged(EntityId, []const u8) = .{}, + + fn deinit(ctx: *LoadContext) void { + ctx.enum_refs.deinit(ctx.db.gpa); + ctx.arena.deinit(); + } +}; + +fn getObject(val: json.Value) !json.ObjectMap { + return switch (val) { + .Object => |obj| obj, + else => return error.NotJsonObject, + }; +} + +fn getArray(val: json.Value) !json.Array { + return switch (val) { + .Array => |arr| arr, + else => return error.NotJsonArray, + }; +} + +// TODO: handle edge cases +fn getIntegerFromObject(obj: json.ObjectMap, comptime T: type, key: []const u8) !?T { + return switch (obj.get(key) orelse return null) { + .Integer => |num| @intCast(T, num), + else => return error.NotJsonInteger, + }; +} + +fn getStringFromObject(obj: json.ObjectMap, key: []const u8) !?[]const u8 { + return switch (obj.get(key) orelse return null) { + .String => |str| str, + else => return error.NotJsonString, + }; +} + +fn entityTypeToString(entity_type: Database.EntityType) []const u8 { + return switch (entity_type) { + .peripheral => "peripherals", + .register_group => "register_groups", + .register => "registers", + .field => "fields", + .@"enum" => "enums", + .enum_field => "enum_fields", + .mode => "modes", + .device => "devices", + .interrupt => "interrupts", + .peripheral_instance => "peripherals", + }; +} + +const string_to_entity_type = std.ComptimeStringMap(Database.EntityType, .{ + .{ "peripherals", .peripheral }, + .{ "register_groups", .register_group }, + .{ "registers", .register }, + .{ "fields", .field }, + .{ "enums", .@"enum" }, + .{ "enum_fields", .enum_field }, + .{ "modes", .mode }, + .{ "interrupts", .interrupt }, +}); + +fn stringToEntityType(str: []const u8) !Database.EntityType { + return if (string_to_entity_type.get(str)) |entity_type| + entity_type + else + error.InvalidEntityType; +} + +// gets stuffed in the arena allocator +fn idToRef( + allocator: std.mem.Allocator, + db: Database, + entity_id: EntityId, +) ![]const u8 { + var ids = std.ArrayList(EntityId).init(allocator); + defer ids.deinit(); + + try ids.append(entity_id); + var tmp_id = entity_id; + while (db.attrs.parent.get(tmp_id)) |parent_id| : (tmp_id = parent_id) { + try ids.insert(0, parent_id); + } + + var ref = std.ArrayList(u8).init(allocator); + defer ref.deinit(); + + const writer = ref.writer(); + const root_type = db.getEntityType(ids.items[0]).?; + + if (root_type.isInstance()) + //try writer.writeAll("instances") + @panic("TODO") + else + try writer.writeAll("types"); + + try writer.print(".{s}.{s}", .{ + entityTypeToString(root_type), + std.zig.fmtId(db.attrs.name.get(ids.items[0]) orelse return error.MissingName), + }); + + for (ids.items[1..]) |id| { + const entity_type = db.getEntityType(id).?; + try writer.print(".children.{s}.{s}", .{ + entityTypeToString(entity_type), + std.zig.fmtId(db.attrs.name.get(id) orelse return error.MissingName), + }); + } + + return ref.toOwnedSlice(); +} + +pub fn loadIntoDb(db: *Database, text: []const u8) !void { + var parser = json.Parser.init(db.gpa, false); + defer parser.deinit(); + + var tree = try parser.parse(text); + defer tree.deinit(); + + if (tree.root != .Object) + return error.NotJsonObject; + + var ctx = LoadContext{ + .db = db, + .arena = std.heap.ArenaAllocator.init(db.gpa), + }; + defer ctx.deinit(); + + if (tree.root.Object.get("types")) |types| + try loadTypes(&ctx, try getObject(types)); + + if (tree.root.Object.get("devices")) |devices| + try loadDevices(&ctx, try getObject(devices)); + + try resolveEnums(&ctx); +} + +fn resolveEnums(ctx: *LoadContext) !void { + const db = ctx.db; + var it = ctx.enum_refs.iterator(); + while (it.next()) |entry| { + const id = entry.key_ptr.*; + const ref = entry.value_ptr.*; + const enum_id = try refToId(db.*, ref); + //assert(db.entityIs("type.enum", enum_id)); + try ctx.db.attrs.@"enum".put(db.gpa, id, enum_id); + } +} + +fn refToId(db: Database, ref: []const u8) !EntityId { + // TODO: do proper tokenization since we'll need to handle @"" fields. okay to leave for now. + var it = std.mem.tokenize(u8, ref, "."); + const first = it.next() orelse return error.Malformed; + return if (std.mem.eql(u8, "types", first)) blk: { + var tmp_id: ?EntityId = null; + break :blk while (true) { + const entity_type = entity_type: { + const str = it.next() orelse return error.Malformed; + break :entity_type try stringToEntityType(str); + }; + + const name = it.next() orelse return error.Malformed; + const last = if (it.next()) |token| + if (std.mem.eql(u8, "children", token)) + false + else + return error.Malformed + else + true; + + if (tmp_id == null) { + tmp_id = tmp_id: inline for (@typeInfo(TypeOfField(Database, "types")).Struct.fields) |field| { + const other_type = try stringToEntityType(field.name); + if (entity_type == other_type) { + var entity_it = @field(db.types, field.name).iterator(); + while (entity_it.next()) |entry| { + const id = entry.key_ptr.*; + if (db.attrs.parent.contains(id)) + continue; + + if (db.attrs.name.get(id)) |other_name| { + if (std.mem.eql(u8, name, other_name)) + break :tmp_id id; + } + } + } + } else return error.RefNotFound; + } else { + tmp_id = tmp_id: inline for (@typeInfo(TypeOfField(Database, "children")).Struct.fields) |field| { + const other_type = try stringToEntityType(field.name); + if (entity_type == other_type) { + if (@field(db.children, field.name).get(tmp_id.?)) |children| { + var child_it = children.iterator(); + while (child_it.next()) |child_entry| { + const child_id = child_entry.key_ptr.*; + if (db.attrs.name.get(child_id)) |other_name| { + if (std.mem.eql(u8, name, other_name)) + break :tmp_id child_id; + } + } + } + } + } else return error.RefNotFound; + } + + if (last) + break tmp_id.?; + } else error.RefNotFound; + } else if (std.mem.eql(u8, "instances", first)) + @panic("TODO") + else + error.Malformed; +} + +fn loadTypes(ctx: *LoadContext, types: json.ObjectMap) !void { + if (types.get("peripherals")) |peripherals| + try loadPeripherals(ctx, try getObject(peripherals)); +} + +fn loadPeripherals(ctx: *LoadContext, peripherals: json.ObjectMap) !void { + var it = peripherals.iterator(); + while (it.next()) |entry| { + const name = entry.key_ptr.*; + const peripheral = entry.value_ptr.*; + try loadPeripheral(ctx, name, try getObject(peripheral)); + } +} + +fn loadPeripheral( + ctx: *LoadContext, + name: []const u8, + peripheral: json.ObjectMap, +) !void { + log.debug("loading peripheral: {s}", .{name}); + const db = ctx.db; + const id = try db.createPeripheral(.{ + .name = name, + .description = try getStringFromObject(peripheral, "description"), + .size = if (peripheral.get("size")) |size_val| + switch (size_val) { + .Integer => |num| @intCast(u64, num), + else => return error.SizeNotInteger, + } + else + null, + }); + errdefer db.destroyEntity(id); + + if (peripheral.get("children")) |children| + try loadChildren(ctx, id, try getObject(children)); +} + +const LoadError = error{ + RefNotFound, + InvalidChildType, + NoPeripheralFound, + MissingEnumFieldValue, + MissingEnumSize, + MissingRegisterOffset, + MissingRegisterSize, + InvalidJsonType, + NotJsonInteger, + NotJsonObject, + NotJsonString, + NotJsonArray, + OutOfMemory, + MissingModeValue, + MissingModeQualifier, + Malformed, + InvalidEntityType, + MissingInterruptIndex, + MissingInstanceType, + MissingInstanceOffset, +}; + +const LoadFn = fn (*LoadContext, EntityId, []const u8, json.ObjectMap) LoadError!void; +const LoadMultipleFn = fn (*LoadContext, EntityId, json.ObjectMap) LoadError!void; +fn loadEntities(comptime load_fn: LoadFn) LoadMultipleFn { + return struct { + fn tmp( + ctx: *LoadContext, + parent_id: EntityId, + entities: json.ObjectMap, + ) LoadError!void { + var it = entities.iterator(); + while (it.next()) |entry| { + const name = entry.key_ptr.*; + const entity = entry.value_ptr.*; + try load_fn(ctx, parent_id, name, try getObject(entity)); + } + } + }.tmp; +} + +const load_fns = struct { + // types + const register_groups = loadEntities(loadRegisterGroup); + const registers = loadEntities(loadRegister); + const fields = loadEntities(loadField); + const enums = loadEntities(loadEnum); + const enum_fields = loadEntities(loadEnumField); + const modes = loadEntities(loadMode); + + // instances + const interrupts = loadEntities(loadInterrupt); + const peripheral_instances = loadEntities(loadPeripheralInstance); +}; + +fn loadChildren( + ctx: *LoadContext, + parent_id: EntityId, + children: json.ObjectMap, +) LoadError!void { + var it = children.iterator(); + while (it.next()) |entry| { + const child_type = entry.key_ptr.*; + const child_map = entry.value_ptr.*; + + inline for (@typeInfo(TypeOfField(Database, "children")).Struct.fields) |field| { + if (std.mem.eql(u8, child_type, field.name)) { + if (@hasDecl(load_fns, field.name)) + try @field(load_fns, field.name)(ctx, parent_id, try getObject(child_map)); + + break; + } + } else if (std.mem.eql(u8, "peripheral_instances", child_type)) { + try load_fns.peripheral_instances(ctx, parent_id, try getObject(child_map)); + } else { + log.err("{s} is not a valid child type", .{child_type}); + return error.InvalidChildType; + } + } +} + +fn loadMode( + ctx: *LoadContext, + parent_id: EntityId, + name: []const u8, + mode: json.ObjectMap, +) LoadError!void { + _ = try ctx.db.createMode(parent_id, .{ + .name = name, + .description = try getStringFromObject(mode, "description"), + .value = (try getStringFromObject(mode, "value")) orelse return error.MissingModeValue, + .qualifier = (try getStringFromObject(mode, "qualifier")) orelse return error.MissingModeQualifier, + }); +} + +fn loadRegisterGroup( + ctx: *LoadContext, + parent_id: EntityId, + name: []const u8, + register_group: json.ObjectMap, +) LoadError!void { + log.debug("load register group", .{}); + const db = ctx.db; + // TODO: probably more + const id = try db.createRegisterGroup(parent_id, .{ + .name = name, + .description = try getStringFromObject(register_group, "description"), + }); + errdefer db.destroyEntity(id); + + if (register_group.get("children")) |children| + try loadChildren(ctx, id, try getObject(children)); +} + +fn loadRegister( + ctx: *LoadContext, + parent_id: EntityId, + name: []const u8, + register: json.ObjectMap, +) LoadError!void { + const db = ctx.db; + const id = try db.createRegister(parent_id, .{ + .name = name, + .description = try getStringFromObject(register, "description"), + .offset = (try getIntegerFromObject(register, u64, "offset")) orelse return error.MissingRegisterOffset, + .size = (try getIntegerFromObject(register, u64, "size")) orelse return error.MissingRegisterSize, + .count = try getIntegerFromObject(register, u64, "count"), + .access = if (try getStringFromObject(register, "access")) |access_str| + std.meta.stringToEnum(Database.Access, access_str) + else + null, + .reset_mask = try getIntegerFromObject(register, u64, "reset_mask"), + .reset_value = try getIntegerFromObject(register, u64, "reset_value"), + }); + errdefer db.destroyEntity(id); + + if (register.get("modes")) |modes| + try loadModes(ctx, id, try getArray(modes)); + + if (register.get("children")) |children| + try loadChildren(ctx, id, try getObject(children)); +} + +fn loadModes( + ctx: *LoadContext, + parent_id: EntityId, + modes: json.Array, +) !void { + const db = ctx.db; + for (modes.items) |mode_val| { + const mode_ref = switch (mode_val) { + .String => |str| str, + else => return error.InvalidJsonType, + }; + + const mode_id = try refToId(db.*, mode_ref); + //assert(db.entityIs("type.mode", mode_id)); + const result = try db.attrs.modes.getOrPut(db.gpa, parent_id); + if (!result.found_existing) + result.value_ptr.* = .{}; + + try result.value_ptr.put(db.gpa, mode_id, {}); + } +} + +fn loadField( + ctx: *LoadContext, + parent_id: EntityId, + name: []const u8, + field: json.ObjectMap, +) LoadError!void { + const db = ctx.db; + const id = try db.createField(parent_id, .{ + .name = name, + .description = try getStringFromObject(field, "description"), + .offset = (try getIntegerFromObject(field, u64, "offset")) orelse return error.MissingRegisterOffset, + .size = (try getIntegerFromObject(field, u64, "size")) orelse return error.MissingRegisterSize, + .count = try getIntegerFromObject(field, u64, "count"), + }); + errdefer db.destroyEntity(id); + + if (field.get("enum")) |enum_val| + switch (enum_val) { + .String => |ref_str| try ctx.enum_refs.put(db.gpa, id, ref_str), + .Object => |enum_obj| { + // peripheral is the parent of enums + // TODO: change this + const peripheral_id = peripheral_id: { + var tmp_id = id; + break :peripheral_id while (db.attrs.parent.get(tmp_id)) |next_id| : (tmp_id = next_id) { + if (.peripheral == db.getEntityType(next_id).?) + break next_id; + } else return error.NoPeripheralFound; + }; + + const enum_id = try loadEnumBase(ctx, peripheral_id, null, enum_obj); + try db.attrs.@"enum".put(db.gpa, id, enum_id); + }, + else => return error.InvalidJsonType, + }; + + if (field.get("children")) |children| + try loadChildren(ctx, id, try getObject(children)); +} + +fn loadEnum( + ctx: *LoadContext, + parent_id: EntityId, + name: []const u8, + enumeration: json.ObjectMap, +) LoadError!void { + _ = try loadEnumBase(ctx, parent_id, name, enumeration); +} + +fn loadEnumBase( + ctx: *LoadContext, + parent_id: EntityId, + name: ?[]const u8, + enumeration: json.ObjectMap, +) LoadError!EntityId { + const db = ctx.db; + const id = try db.createEnum(parent_id, .{ + .name = name, + .description = try getStringFromObject(enumeration, "description"), + .size = (try getIntegerFromObject(enumeration, u64, "size")) orelse return error.MissingEnumSize, + }); + errdefer db.destroyEntity(id); + + if (enumeration.get("children")) |children| + try loadChildren(ctx, id, try getObject(children)); + + return id; +} + +fn loadEnumField( + ctx: *LoadContext, + parent_id: EntityId, + name: []const u8, + enum_field: json.ObjectMap, +) LoadError!void { + const db = ctx.db; + + const id = try db.createEnumField(parent_id, .{ + .name = name, + .description = try getStringFromObject(enum_field, "description"), + .value = (try getIntegerFromObject(enum_field, u32, "value")) orelse return error.MissingEnumFieldValue, + }); + + if (enum_field.get("children")) |children| + try loadChildren(ctx, id, try getObject(children)); +} + +fn loadDevices(ctx: *LoadContext, devices: json.ObjectMap) !void { + var it = devices.iterator(); + while (it.next()) |entry| { + const name = entry.key_ptr.*; + const device = entry.value_ptr.*; + try loadDevice(ctx, name, try getObject(device)); + } +} + +fn loadDevice(ctx: *LoadContext, name: []const u8, device: json.ObjectMap) !void { + log.debug("loading device: {s}", .{name}); + const db = ctx.db; + const id = try db.createDevice(.{ + .name = name, + .description = try getStringFromObject(device, "description"), + .arch = if (device.get("arch")) |arch_val| + switch (arch_val) { + .String => |arch_str| std.meta.stringToEnum(Database.Arch, arch_str) orelse return error.InvalidArch, + else => return error.InvalidJsonType, + } + else + .unknown, + }); + errdefer db.destroyEntity(id); + + if (device.get("properties")) |properties| + try loadProperties(ctx, id, try getObject(properties)); + + if (device.get("children")) |children| + try loadChildren(ctx, id, try getObject(children)); +} + +fn loadProperties(ctx: *LoadContext, device_id: EntityId, properties: json.ObjectMap) !void { + const db = ctx.db; + var it = properties.iterator(); + while (it.next()) |entry| { + const key = entry.key_ptr.*; + const value = switch (entry.value_ptr.*) { + .String => |str| str, + else => return error.InvalidJsonType, + }; + + try db.addDeviceProperty(device_id, key, value); + } +} + +fn loadInterrupt( + ctx: *LoadContext, + device_id: EntityId, + name: []const u8, + interrupt: json.ObjectMap, +) LoadError!void { + _ = try ctx.db.createInterrupt(device_id, .{ + .name = name, + .description = try getStringFromObject(interrupt, "description"), + .index = (try getIntegerFromObject(interrupt, i32, "index")) orelse return error.MissingInterruptIndex, + }); +} + +fn loadPeripheralInstance( + ctx: *LoadContext, + device_id: EntityId, + name: []const u8, + peripheral: json.ObjectMap, +) !void { + const db = ctx.db; + const type_ref = (try getStringFromObject(peripheral, "type")) orelse return error.MissingInstanceType; + const type_id = try refToId(db.*, type_ref); + _ = try ctx.db.createPeripheralInstance(device_id, type_id, .{ + .name = name, + .description = try getStringFromObject(peripheral, "description"), + .offset = (try getIntegerFromObject(peripheral, u64, "offset")) orelse return error.MissingInstanceOffset, + .count = try getIntegerFromObject(peripheral, u64, "count"), + }); } pub fn toJson(db: Database) !json.ValueTree { @@ -25,23 +610,23 @@ pub fn toJson(db: Database) !json.ValueTree { var types = json.ObjectMap.init(allocator); var devices = json.ObjectMap.init(allocator); - // this is a string map to ensure there are no typename collisions - var types_to_populate = std.StringArrayHashMap(EntityId).init(allocator); - var device_it = db.instances.devices.iterator(); while (device_it.next()) |entry| try populateDevice( db, &arena, - &types_to_populate, &devices, entry.key_ptr.*, ); + try root.put("version", .{ .String = schema_version }); try populateTypes(db, &arena, &types); - try root.put("types", .{ .Object = types }); + if (types.count() > 0) + try root.put("types", .{ .Object = types }); + + if (devices.count() > 0) + try root.put("devices", .{ .Object = devices }); - try root.put("devices", .{ .Object = devices }); return json.ValueTree{ .arena = arena, .root = .{ .Object = root }, @@ -54,14 +639,18 @@ fn populateTypes( types: *json.ObjectMap, ) !void { const allocator = arena.allocator(); + var peripherals = json.ObjectMap.init(allocator); var it = db.types.peripherals.iterator(); while (it.next()) |entry| { const periph_id = entry.key_ptr.*; const name = db.attrs.name.get(periph_id) orelse continue; var typ = json.ObjectMap.init(allocator); try populateType(db, arena, periph_id, &typ); - try types.put(name, .{ .Object = typ }); + try peripherals.put(name, .{ .Object = typ }); } + + if (peripherals.count() > 0) + try types.put("peripherals", .{ .Object = peripherals }); } fn populateType( @@ -80,6 +669,9 @@ fn populateType( if (db.attrs.size.get(id)) |size| try typ.put("size", .{ .Integer = @intCast(i64, size) }); + if (db.attrs.count.get(id)) |count| + try typ.put("count", .{ .Integer = @intCast(i64, count) }); + if (db.attrs.reset_value.get(id)) |reset_value| try typ.put("reset_value", .{ .Integer = @intCast(i64, reset_value) }); @@ -98,17 +690,32 @@ fn populateType( }, }); - if (db.attrs.@"enum".get(id)) |enum_id| - if (db.attrs.name.get(enum_id)) |enum_name| - try typ.put("enum", .{ .String = enum_name }); + if (db.attrs.@"enum".get(id)) |enum_id| { + if (db.attrs.name.contains(enum_id)) { + const ref = try idToRef(arena.allocator(), db, enum_id); + try typ.put("enum", .{ .String = ref }); + } else { + var anon_enum = json.ObjectMap.init(allocator); + try populateType(db, arena, enum_id, &anon_enum); + try typ.put("enum", .{ .Object = anon_enum }); + } + } if (db.attrs.modes.get(id)) |modeset| { var modearray = json.Array.init(allocator); var it = modeset.iterator(); - while (it.next()) |entry| - if (db.attrs.name.get(entry.key_ptr.*)) |mode_name| - try modearray.append(.{ .String = mode_name }); + while (it.next()) |entry| { + const mode_id = entry.key_ptr.*; + if (db.attrs.name.contains(mode_id)) { + const ref = try idToRef( + arena.allocator(), + db, + mode_id, + ); + try modearray.append(.{ .String = ref }); + } else return error.MissingModeName; + } if (modearray.items.len > 0) try typ.put("modes", .{ .Array = modearray }); @@ -148,7 +755,6 @@ fn populateType( fn populateDevice( db: Database, arena: *ArenaAllocator, - types_to_populate: *std.StringArrayHashMap(EntityId), devices: *json.ObjectMap, id: EntityId, ) !void { @@ -176,7 +782,6 @@ fn populateDevice( try populatePeripheral( db, arena, - types_to_populate, &peripherals, entry.key_ptr.*, entry.value_ptr.*, @@ -184,15 +789,21 @@ fn populateDevice( const arch = db.instances.devices.get(id).?.arch; try device.put("arch", .{ .String = arch.toString() }); + if (db.attrs.description.get(id)) |description| + try device.put("description", .{ .String = description }); if (properties.count() > 0) try device.put("properties", .{ .Object = properties }); + var device_children = json.ObjectMap.init(allocator); if (interrupts.count() > 0) - try device.put("interrupts", .{ .Object = interrupts }); + try device_children.put("interrupts", .{ .Object = interrupts }); if (peripherals.count() > 0) - try device.put("peripherals", .{ .Object = peripherals }); + try device_children.put("peripheral_instances", .{ .Object = peripherals }); + + if (device_children.count() > 0) + try device.put("children", .{ .Object = device_children }); try devices.put(name, .{ .Object = device }); } @@ -218,7 +829,6 @@ fn populateInterrupt( fn populatePeripheral( db: Database, arena: *ArenaAllocator, - types_to_populate: *std.StringArrayHashMap(EntityId), peripherals: *json.ObjectMap, id: EntityId, type_id: EntityId, @@ -235,27 +845,838 @@ fn populatePeripheral( if (db.attrs.version.get(id)) |version| try peripheral.put("version", .{ .String = version }); - // if the peripheral instance's type is named, then we add it to the list - // of types to populate - if (db.attrs.name.get(type_id)) |type_name| { - // TODO: handle collisions -- will need to inline the type - try types_to_populate.put(type_name, type_id); - try peripheral.put("type", .{ .String = type_name }); - } else { - var typ = json.ObjectMap.init(allocator); - try populateType(db, arena, type_id, &typ); - try peripheral.put("type", .{ .Object = typ }); - } + if (db.attrs.count.get(id)) |count| + try peripheral.put("count", .{ .Integer = @intCast(i64, count) }); + + // TODO: handle collisions -- will need to inline the type + const type_ref = try idToRef( + arena.allocator(), + db, + type_id, + ); + try peripheral.put("type", .{ .String = type_ref }); // TODO: peripheral instance children try peripherals.put(name, .{ .Object = peripheral }); } +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const expectEqualStrings = std.testing.expectEqualStrings; +const DbInitFn = fn (allocator: std.mem.Allocator) anyerror!Database; +const tests = @import("output_tests.zig"); + +test "refToId" { + var db = try tests.peripheralWithModes(std.testing.allocator); + defer db.deinit(); + + const mode_id = try db.getEntityIdByName("type.mode", "TEST_MODE1"); + const mode_ref = try idToRef(std.testing.allocator, db, mode_id); + defer std.testing.allocator.free(mode_ref); + + try expectEqualStrings( + "types.peripherals.TEST_PERIPHERAL.children.modes.TEST_MODE1", + mode_ref, + ); +} + +test "idToRef" { + var db = try tests.peripheralWithModes(std.testing.allocator); + defer db.deinit(); + + const expected_mode_id = try db.getEntityIdByName("type.mode", "TEST_MODE1"); + const actual_mode_id = try refToId( + db, + "types.peripherals.TEST_PERIPHERAL.children.modes.TEST_MODE1", + ); + + try expectEqual(expected_mode_id, actual_mode_id); +} + // ============================================================================= // loadIntoDb Tests // ============================================================================= +fn loadTest(comptime init: DbInitFn, input: []const u8) !void { + var expected = try init(std.testing.allocator); + defer expected.deinit(); + + const copy = try std.testing.allocator.dupe(u8, input); + var actual = Database.initFromJson(std.testing.allocator, copy) catch |err| { + std.testing.allocator.free(copy); + return err; + }; + defer actual.deinit(); + + // freeing explicitly here to invalidate the memory for input + std.testing.allocator.free(copy); + try testing.expectEqualDatabases(expected, actual); +} + +test "regzon.load.empty" { + try loadTest(emptyDb, json_data.empty); +} + +test "regzon.load.peripheral type with register and field" { + try loadTest( + tests.peripheralTypeWithRegisterAndField, + json_data.peripheral_type_with_register_and_field, + ); +} + +test "regzon.load.peripheral instantiation" { + try loadTest( + tests.peripheralInstantiation, + json_data.peripheral_instantiation, + ); +} + +test "regzon.load.peripherals with a shared type" { + try loadTest( + tests.peripheralsWithSharedType, + json_data.peripherals_with_shared_type, + ); +} + +test "regzon.load.peripheral with modes" { + try loadTest( + tests.peripheralWithModes, + json_data.peripherals_with_modes, + ); +} + +test "regzon.load.field with named enum" { + try loadTest( + tests.fieldWithNamedEnum, + json_data.field_with_named_enum, + ); +} + +test "regzon.load.field with anonymous enum" { + try loadTest( + tests.fieldWithAnonymousEnum, + json_data.field_with_anonymous_enum, + ); +} + +test "regzon.load.namespaced register groups" { + try loadTest( + tests.namespacedRegisterGroups, + json_data.namespaced_register_groups, + ); +} + +test "regzon.load.peripheral with count" { + try loadTest( + tests.peripheralWithCount, + json_data.peripheral_with_count, + ); +} + +test "regzon.load.register with count" { + try loadTest( + tests.registerWithCount, + json_data.register_with_count, + ); +} + +test "regzon.load.register with count and fields" { + try loadTest( + tests.registerWithCountAndFields, + json_data.register_with_count_and_fields, + ); +} + +test "regzon.load.field with count, width of one, offset, and padding" { + try loadTest( + tests.fieldWithCountWidthOfOneOffsetAndPadding, + json_data.field_with_count_width_of_one_offset_and_padding, + ); +} + +test "regzon.load.field_with_count_multibit_width_offset_and_padding" { + try loadTest( + tests.fieldWithCountMultiBitWidthOffsetAndPadding, + json_data.field_with_count_multibit_width_offset_and_padding, + ); +} + +test "regzon.load.interruptsAvr" { + try loadTest( + tests.interruptsAvr, + json_data.interrupts_avr, + ); +} + +// ============================================================================= +// jsonStringify Tests +// ============================================================================= +fn stringifyTest(comptime init: DbInitFn, expected: []const u8) !void { + var db = try init(std.testing.allocator); + defer db.deinit(); + + var buffer = std.ArrayList(u8).init(std.testing.allocator); + defer buffer.deinit(); + + const test_stringify_opts = .{ + .whitespace = .{ + .indent_level = 0, + .indent = .{ .Space = 2 }, + }, + }; + + try db.jsonStringify(test_stringify_opts, buffer.writer()); + try expectEqualStrings(expected, buffer.items); +} + +fn emptyDb(allocator: Allocator) !Database { + return Database.init(allocator); +} + +test "regzon.jsonStringify.empty" { + try stringifyTest(emptyDb, json_data.empty); +} + +test "regzon.jsonStringify.peripheral type with register and field" { + try stringifyTest( + tests.peripheralTypeWithRegisterAndField, + json_data.peripheral_type_with_register_and_field, + ); +} + +test "regzon.jsonStringify.peripheral instantiation" { + try stringifyTest( + tests.peripheralInstantiation, + json_data.peripheral_instantiation, + ); +} + +test "regzon.jsonStringify.peripherals with a shared type" { + try stringifyTest( + tests.peripheralsWithSharedType, + json_data.peripherals_with_shared_type, + ); +} + +test "regzon.jsonStringify.peripheral with modes" { + try stringifyTest( + tests.peripheralWithModes, + json_data.peripherals_with_modes, + ); +} + +test "regzon.jsonStringify.field with named enum" { + try stringifyTest( + tests.fieldWithNamedEnum, + json_data.field_with_named_enum, + ); +} + +test "regzon.jsonStringify.field with anonymous enum" { + try stringifyTest( + tests.fieldWithAnonymousEnum, + json_data.field_with_anonymous_enum, + ); +} + +test "regzon.jsonStringify.namespaced register groups" { + try stringifyTest( + tests.namespacedRegisterGroups, + json_data.namespaced_register_groups, + ); +} + +test "regzon.jsonStringify.peripheral with count" { + try stringifyTest( + tests.peripheralWithCount, + json_data.peripheral_with_count, + ); +} + +test "regzon.jsonStringify.register with count" { + try stringifyTest( + tests.registerWithCount, + json_data.register_with_count, + ); +} + +test "regzon.jsonStringify.register with count and fields" { + try stringifyTest( + tests.registerWithCountAndFields, + json_data.register_with_count_and_fields, + ); +} + +test "regzon.jsonStringify.field with count, width of one, offset, and padding" { + try stringifyTest( + tests.fieldWithCountWidthOfOneOffsetAndPadding, + json_data.field_with_count_width_of_one_offset_and_padding, + ); +} + +test "regzon.jsonStringify.field_with_count_multibit_width_offset_and_padding" { + try stringifyTest( + tests.fieldWithCountMultiBitWidthOffsetAndPadding, + json_data.field_with_count_multibit_width_offset_and_padding, + ); +} + +test "regzon.jsonStringify.interruptsAvr" { + try stringifyTest( + tests.interruptsAvr, + json_data.interrupts_avr, + ); +} // ============================================================================= -// toJson Tests +// Test data // ============================================================================= + +fn versionize(comptime version: []const u8, comptime json_str: []const u8) []const u8 { + const template = ""; + const index = std.mem.indexOf(u8, json_str, template) orelse unreachable; // gotta specify version + + return json_str[0..index] ++ version ++ json_str[index + template.len ..]; +} + +const json_data = struct { + const empty = versionize(schema_version, + \\{ + \\ "version": "" + \\} + ); + + const peripheral_type_with_register_and_field = versionize(schema_version, + \\{ + \\ "version": "", + \\ "types": { + \\ "peripherals": { + \\ "TEST_PERIPHERAL": { + \\ "children": { + \\ "registers": { + \\ "TEST_REGISTER": { + \\ "offset": 0, + \\ "size": 32, + \\ "children": { + \\ "fields": { + \\ "TEST_FIELD": { + \\ "offset": 0, + \\ "size": 1 + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\} + ); + + const peripheral_instantiation = versionize(schema_version, + \\{ + \\ "version": "", + \\ "types": { + \\ "peripherals": { + \\ "TEST_PERIPHERAL": { + \\ "children": { + \\ "registers": { + \\ "TEST_REGISTER": { + \\ "offset": 0, + \\ "size": 32, + \\ "children": { + \\ "fields": { + \\ "TEST_FIELD": { + \\ "offset": 0, + \\ "size": 1 + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ }, + \\ "devices": { + \\ "TEST_DEVICE": { + \\ "arch": "unknown", + \\ "children": { + \\ "peripheral_instances": { + \\ "TEST0": { + \\ "offset": 4096, + \\ "type": "types.peripherals.TEST_PERIPHERAL" + \\ } + \\ } + \\ } + \\ } + \\ } + \\} + ); + + const peripherals_with_shared_type = versionize(schema_version, + \\{ + \\ "version": "", + \\ "types": { + \\ "peripherals": { + \\ "TEST_PERIPHERAL": { + \\ "children": { + \\ "registers": { + \\ "TEST_REGISTER": { + \\ "offset": 0, + \\ "size": 32, + \\ "children": { + \\ "fields": { + \\ "TEST_FIELD": { + \\ "offset": 0, + \\ "size": 1 + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ }, + \\ "devices": { + \\ "TEST_DEVICE": { + \\ "arch": "unknown", + \\ "children": { + \\ "peripheral_instances": { + \\ "TEST0": { + \\ "offset": 4096, + \\ "type": "types.peripherals.TEST_PERIPHERAL" + \\ }, + \\ "TEST1": { + \\ "offset": 8192, + \\ "type": "types.peripherals.TEST_PERIPHERAL" + \\ } + \\ } + \\ } + \\ } + \\ } + \\} + ); + + const peripherals_with_modes = versionize(schema_version, + \\{ + \\ "version": "", + \\ "types": { + \\ "peripherals": { + \\ "TEST_PERIPHERAL": { + \\ "children": { + \\ "modes": { + \\ "TEST_MODE1": { + \\ "value": "0x00", + \\ "qualifier": "TEST_PERIPHERAL.TEST_MODE1.COMMON_REGISTER.TEST_FIELD" + \\ }, + \\ "TEST_MODE2": { + \\ "value": "0x01", + \\ "qualifier": "TEST_PERIPHERAL.TEST_MODE2.COMMON_REGISTER.TEST_FIELD" + \\ } + \\ }, + \\ "registers": { + \\ "TEST_REGISTER1": { + \\ "offset": 0, + \\ "size": 32, + \\ "modes": [ + \\ "types.peripherals.TEST_PERIPHERAL.children.modes.TEST_MODE1" + \\ ] + \\ }, + \\ "TEST_REGISTER2": { + \\ "offset": 0, + \\ "size": 32, + \\ "modes": [ + \\ "types.peripherals.TEST_PERIPHERAL.children.modes.TEST_MODE2" + \\ ] + \\ }, + \\ "COMMON_REGISTER": { + \\ "offset": 4, + \\ "size": 32, + \\ "children": { + \\ "fields": { + \\ "TEST_FIELD": { + \\ "offset": 0, + \\ "size": 1 + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\} + ); + + const field_with_named_enum = versionize(schema_version, + \\{ + \\ "version": "", + \\ "types": { + \\ "peripherals": { + \\ "TEST_PERIPHERAL": { + \\ "children": { + \\ "registers": { + \\ "TEST_REGISTER": { + \\ "offset": 0, + \\ "size": 8, + \\ "children": { + \\ "fields": { + \\ "TEST_FIELD": { + \\ "offset": 0, + \\ "size": 4, + \\ "enum": "types.peripherals.TEST_PERIPHERAL.children.enums.TEST_ENUM" + \\ } + \\ } + \\ } + \\ } + \\ }, + \\ "enums": { + \\ "TEST_ENUM": { + \\ "size": 4, + \\ "children": { + \\ "enum_fields": { + \\ "TEST_ENUM_FIELD1": { + \\ "value": 0 + \\ }, + \\ "TEST_ENUM_FIELD2": { + \\ "value": 1 + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\} + ); + + const field_with_anonymous_enum = versionize(schema_version, + \\{ + \\ "version": "", + \\ "types": { + \\ "peripherals": { + \\ "TEST_PERIPHERAL": { + \\ "children": { + \\ "registers": { + \\ "TEST_REGISTER": { + \\ "offset": 0, + \\ "size": 8, + \\ "children": { + \\ "fields": { + \\ "TEST_FIELD": { + \\ "offset": 0, + \\ "size": 4, + \\ "enum": { + \\ "size": 4, + \\ "children": { + \\ "enum_fields": { + \\ "TEST_ENUM_FIELD1": { + \\ "value": 0 + \\ }, + \\ "TEST_ENUM_FIELD2": { + \\ "value": 1 + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\} + ); + + const namespaced_register_groups = versionize(schema_version, + \\{ + \\ "version": "", + \\ "types": { + \\ "peripherals": { + \\ "PORT": { + \\ "children": { + \\ "register_groups": { + \\ "PORTB": { + \\ "children": { + \\ "registers": { + \\ "PORTB": { + \\ "offset": 0, + \\ "size": 8 + \\ }, + \\ "DDRB": { + \\ "offset": 1, + \\ "size": 8 + \\ }, + \\ "PINB": { + \\ "offset": 2, + \\ "size": 8 + \\ } + \\ } + \\ } + \\ }, + \\ "PORTC": { + \\ "children": { + \\ "registers": { + \\ "PORTC": { + \\ "offset": 0, + \\ "size": 8 + \\ }, + \\ "DDRC": { + \\ "offset": 1, + \\ "size": 8 + \\ }, + \\ "PINC": { + \\ "offset": 2, + \\ "size": 8 + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ }, + \\ "devices": { + \\ "ATmega328P": { + \\ "arch": "unknown", + \\ "children": { + \\ "peripheral_instances": { + \\ "PORTB": { + \\ "offset": 35, + \\ "type": "types.peripherals.PORT.children.register_groups.PORTB" + \\ }, + \\ "PORTC": { + \\ "offset": 38, + \\ "type": "types.peripherals.PORT.children.register_groups.PORTC" + \\ } + \\ } + \\ } + \\ } + \\ } + \\} + ); + + const peripheral_with_count = versionize(schema_version, + \\{ + \\ "version": "", + \\ "types": { + \\ "peripherals": { + \\ "PORTB": { + \\ "size": 3, + \\ "children": { + \\ "registers": { + \\ "PORTB": { + \\ "offset": 0, + \\ "size": 8 + \\ }, + \\ "DDRB": { + \\ "offset": 1, + \\ "size": 8 + \\ }, + \\ "PINB": { + \\ "offset": 2, + \\ "size": 8 + \\ } + \\ } + \\ } + \\ } + \\ } + \\ }, + \\ "devices": { + \\ "ATmega328P": { + \\ "arch": "unknown", + \\ "children": { + \\ "peripheral_instances": { + \\ "PORTB": { + \\ "offset": 35, + \\ "count": 4, + \\ "type": "types.peripherals.PORTB" + \\ } + \\ } + \\ } + \\ } + \\ } + \\} + ); + + const register_with_count = versionize(schema_version, + \\{ + \\ "version": "", + \\ "types": { + \\ "peripherals": { + \\ "PORTB": { + \\ "children": { + \\ "registers": { + \\ "PORTB": { + \\ "offset": 0, + \\ "size": 8, + \\ "count": 4 + \\ }, + \\ "DDRB": { + \\ "offset": 4, + \\ "size": 8 + \\ }, + \\ "PINB": { + \\ "offset": 5, + \\ "size": 8 + \\ } + \\ } + \\ } + \\ } + \\ } + \\ }, + \\ "devices": { + \\ "ATmega328P": { + \\ "arch": "unknown", + \\ "children": { + \\ "peripheral_instances": { + \\ "PORTB": { + \\ "offset": 35, + \\ "type": "types.peripherals.PORTB" + \\ } + \\ } + \\ } + \\ } + \\ } + \\} + ); + + const register_with_count_and_fields = versionize(schema_version, + \\{ + \\ "version": "", + \\ "types": { + \\ "peripherals": { + \\ "PORTB": { + \\ "children": { + \\ "registers": { + \\ "PORTB": { + \\ "offset": 0, + \\ "size": 8, + \\ "count": 4, + \\ "children": { + \\ "fields": { + \\ "TEST_FIELD": { + \\ "offset": 0, + \\ "size": 4 + \\ } + \\ } + \\ } + \\ }, + \\ "DDRB": { + \\ "offset": 4, + \\ "size": 8 + \\ }, + \\ "PINB": { + \\ "offset": 5, + \\ "size": 8 + \\ } + \\ } + \\ } + \\ } + \\ } + \\ }, + \\ "devices": { + \\ "ATmega328P": { + \\ "arch": "unknown", + \\ "children": { + \\ "peripheral_instances": { + \\ "PORTB": { + \\ "offset": 35, + \\ "type": "types.peripherals.PORTB" + \\ } + \\ } + \\ } + \\ } + \\ } + \\} + ); + + const field_with_count_width_of_one_offset_and_padding = versionize(schema_version, + \\{ + \\ "version": "", + \\ "types": { + \\ "peripherals": { + \\ "PORTB": { + \\ "children": { + \\ "registers": { + \\ "PORTB": { + \\ "offset": 0, + \\ "size": 8, + \\ "children": { + \\ "fields": { + \\ "TEST_FIELD": { + \\ "offset": 2, + \\ "size": 1, + \\ "count": 5 + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\} + ); + + const field_with_count_multibit_width_offset_and_padding = versionize(schema_version, + \\{ + \\ "version": "", + \\ "types": { + \\ "peripherals": { + \\ "PORTB": { + \\ "children": { + \\ "registers": { + \\ "PORTB": { + \\ "offset": 0, + \\ "size": 8, + \\ "children": { + \\ "fields": { + \\ "TEST_FIELD": { + \\ "offset": 2, + \\ "size": 2, + \\ "count": 2 + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\ } + \\} + ); + + const interrupts_avr = versionize(schema_version, + \\{ + \\ "version": "", + \\ "devices": { + \\ "ATmega328P": { + \\ "arch": "avr8", + \\ "children": { + \\ "interrupts": { + \\ "TEST_VECTOR1": { + \\ "index": 1 + \\ }, + \\ "TEST_VECTOR2": { + \\ "index": 3 + \\ } + \\ } + \\ } + \\ } + \\ } + \\} + ); +}; diff --git a/tools/regz/src/testing.zig b/tools/regz/src/testing.zig index 53204f9..a38089f 100644 --- a/tools/regz/src/testing.zig +++ b/tools/regz/src/testing.zig @@ -38,3 +38,200 @@ pub fn expectAttr( else => try expectEqual(expected, @field(db.attrs, attr_name).get(id).?), } } + +const DatabaseAndId = struct { + db: Database, + id: EntityId, +}; + +fn expectEqualAttr( + comptime attr_name: []const u8, + expected: DatabaseAndId, + actual: DatabaseAndId, +) !void { + const found = @field(expected.db.attrs, attr_name).contains(expected.id); + try expectEqual( + found, + @field(actual.db.attrs, attr_name).contains(actual.id), + ); + + if (!found) + return; + + const expected_value = @field(expected.db.attrs, attr_name).get(expected.id).?; + const actual_value = @field(actual.db.attrs, attr_name).get(actual.id).?; + switch (Attr(attr_name)) { + []const u8 => try expectEqualStrings(expected_value, actual_value), + else => try expectEqual(expected_value, actual_value), + } +} + +fn expectEqualAttrs( + expected: DatabaseAndId, + actual: DatabaseAndId, +) !void { + // skip name since that's usually been compared + + try expectEqualAttr("description", expected, actual); + try expectEqualAttr("offset", expected, actual); + try expectEqualAttr("access", expected, actual); + try expectEqualAttr("count", expected, actual); + try expectEqualAttr("size", expected, actual); + try expectEqualAttr("reset_value", expected, actual); + try expectEqualAttr("reset_mask", expected, actual); + try expectEqualAttr("version", expected, actual); + + // TODO: + // - modes + // - enum +} + +pub fn expectEqualDatabases( + expected: Database, + actual: Database, +) !void { + var it = expected.types.peripherals.iterator(); + while (it.next()) |entry| { + const peripheral_id = entry.key_ptr.*; + const name = expected.attrs.name.get(peripheral_id) orelse unreachable; + std.log.debug("peripheral: {s}", .{name}); + const expected_id = try expected.getEntityIdByName("type.peripheral", name); + const actual_id = try actual.getEntityIdByName("type.peripheral", name); + + try expectEqualEntities( + .{ .db = expected, .id = expected_id }, + .{ .db = actual, .id = actual_id }, + ); + } + + // difficult to debug, but broad checks + inline for (@typeInfo(TypeOfField(Database, "attrs")).Struct.fields) |field| { + std.log.debug("attr: {s}", .{field.name}); + try expectEqual( + @field(expected.attrs, field.name).count(), + @field(actual.attrs, field.name).count(), + ); + } + + inline for (@typeInfo(TypeOfField(Database, "children")).Struct.fields) |field| { + std.log.debug("child: {s}", .{field.name}); + try expectEqual( + @field(expected.children, field.name).count(), + @field(actual.children, field.name).count(), + ); + } + + inline for (@typeInfo(TypeOfField(Database, "types")).Struct.fields) |field| { + std.log.debug("type: {s}", .{field.name}); + try expectEqual( + @field(expected.types, field.name).count(), + @field(actual.types, field.name).count(), + ); + } + + inline for (@typeInfo(TypeOfField(Database, "instances")).Struct.fields) |field| { + std.log.debug("instance: {s}", .{field.name}); + try expectEqual( + @field(expected.instances, field.name).count(), + @field(actual.instances, field.name).count(), + ); + } +} + +const ErrorEqualEntities = error{ + TestExpectedEqual, + NameNotFound, + TestUnexpectedResult, +}; + +fn expectEqualEntities( + expected: DatabaseAndId, + actual: DatabaseAndId, +) ErrorEqualEntities!void { + const expected_type = expected.db.getEntityType(expected.id).?; + const actual_type = actual.db.getEntityType(actual.id).?; + try expectEqual(expected_type, actual_type); + + switch (expected_type) { + .enum_field => { + const expected_value = expected.db.types.enum_fields.get(expected.id).?; + const actual_value = actual.db.types.enum_fields.get(actual.id).?; + try expectEqual(expected_value, actual_value); + }, + .mode => { + const expected_mode = expected.db.types.modes.get(expected.id).?; + const actual_mode = actual.db.types.modes.get(actual.id).?; + try expectEqualStrings(expected_mode.qualifier, actual_mode.qualifier); + try expectEqualStrings(expected_mode.value, actual_mode.value); + }, + .interrupt => { + const expected_value = expected.db.instances.interrupts.get(expected.id).?; + const actual_value = actual.db.instances.interrupts.get(actual.id).?; + try expectEqual(expected_value, actual_value); + }, + .peripheral_instance => { + const expected_id = expected.db.instances.peripherals.get(expected.id).?; + const actual_id = actual.db.instances.peripherals.get(actual.id).?; + try expectEqualEntities( + .{ .db = expected.db, .id = expected_id }, + .{ .db = actual.db, .id = actual_id }, + ); + }, + .device => { + const expected_device = expected.db.instances.devices.get(expected.id).?; + const actual_device = actual.db.instances.devices.get(actual.id).?; + try expectEqual(expected_device.arch, actual_device.arch); + + // properties + try expectEqual( + expected_device.properties.count(), + actual_device.properties.count(), + ); + + var it = expected_device.properties.iterator(); + while (it.next()) |entry| { + const key = entry.key_ptr.*; + const expected_value = entry.value_ptr.*; + try expect(actual_device.properties.contains(key)); + try expectEqualStrings( + expected_value, + actual_device.properties.get(key).?, + ); + } + }, + + else => {}, + } + + try expectEqualAttrs(expected, actual); + try expectEqualChildren(expected, actual); +} + +fn expectEqualChildren( + expected: DatabaseAndId, + actual: DatabaseAndId, +) ErrorEqualEntities!void { + inline for (@typeInfo(TypeOfField(Database, "children")).Struct.fields) |field| { + if (@field(expected.db.children, field.name).get(expected.id)) |entity_set| { + var it = entity_set.iterator(); + while (it.next()) |entry| { + const expected_child_id = entry.key_ptr.*; + const expected_child_name = expected.db.attrs.name.get(expected_child_id) orelse continue; + + try expect(@field(actual.db.children, field.name).contains(actual.id)); + var actual_it = @field(actual.db.children, field.name).get(actual.id).?.iterator(); + const actual_child_id = while (actual_it.next()) |child_entry| { + const child_id = child_entry.key_ptr.*; + const actual_child_name = actual.db.attrs.name.get(child_id) orelse continue; + if (std.mem.eql(u8, expected_child_name, actual_child_name)) + break child_id; + } else return error.NameNotFound; + + try expectEqualEntities( + .{ .db = expected.db, .id = expected_child_id }, + .{ .db = actual.db, .id = actual_child_id }, + ); + } + } + } +}