From 22eb995d38dc4ff89d423f715385ff28c12aac8f Mon Sep 17 00:00:00 2001 From: Nulo Date: Tue, 1 Feb 2022 21:17:33 -0300 Subject: [PATCH] =?UTF-8?q?=E2=80=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + build.zig | 17 +++++ src/main.zig | 204 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 223 insertions(+) create mode 100644 .gitignore create mode 100644 build.zig create mode 100644 src/main.zig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6e2253f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +zig-cache +src/zig-cache diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..a46728d --- /dev/null +++ b/build.zig @@ -0,0 +1,17 @@ +const std = @import("std"); + +pub fn build(b: *std.build.Builder) void { + // Standard release options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. + const mode = b.standardReleaseOptions(); + + const lib = b.addStaticLibrary("ini-parser", "src/main.zig"); + lib.setBuildMode(mode); + lib.install(); + + var main_tests = b.addTest("src/main.zig"); + main_tests.setBuildMode(mode); + + const test_step = b.step("test", "Run library tests"); + test_step.dependOn(&main_tests.step); +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..e4c653b --- /dev/null +++ b/src/main.zig @@ -0,0 +1,204 @@ +const std = @import("std"); +const testing = std.testing; + +const Self = @This(); + +pub const KeyValue = struct { + key: []const u8, + value: []const u8, +}; + +pub const Section = struct { + pub const KeyValues = std.ArrayList(KeyValue); + name: []const u8, + key_values: KeyValues, + pub fn init(allocator: std.mem.Allocator, name: []const u8) Section { + return Section{ + .name = name, + .key_values = KeyValues.init(allocator), + }; + } + pub fn deinit(self: Section) void { + self.key_values.deinit(); + } +}; + +fn scrapComments(str: []const u8) []const u8 { + if (std.mem.indexOf(u8, str, ";")) |index| { + return std.mem.trim(u8, str[0..index], &std.ascii.spaces); + } else { + return std.mem.trim(u8, str, &std.ascii.spaces); + } +} + +pub const Sections = std.ArrayList(Section); +allocator: std.mem.Allocator, +sections: Sections, + +pub fn parse(allocator: std.mem.Allocator, str: []const u8) !Self { + var self = Self{ + .allocator = allocator, + .sections = Sections.init(allocator), + }; + errdefer self.sections.deinit(); + errdefer for (self.sections.items) |section| + section.deinit(); + + var iter = std.mem.tokenize(u8, str, "\n"); + var line_num: usize = 1; + while (iter.next()) |line| { + defer line_num += 1; + const actual_line = scrapComments(line); + if (actual_line.len == 0) { + continue; + } else if (actual_line[0] == '[') { + var section_name = actual_line[1..]; + if (actual_line[actual_line.len - 1] != ']') { + std.log.warn("Invalid section name at line {}", .{line_num}); + } else { + section_name = section_name[0 .. section_name.len - 1]; + } + try self.sections.append(Section.init(allocator, section_name)); + } else { + if (std.mem.indexOf(u8, actual_line, "=")) |index| { + const key = actual_line[0..index]; + const value = actual_line[index + 1 ..]; + if (self.sections.items.len > 0) { + try self.sections.items[self.sections.items.len - 1] + .key_values.append(.{ .key = key, .value = value }); + } else { + std.log.warn("Ignored key-value without section at line {}", .{line_num}); + } + } else { + std.log.warn("Ignored invalid key-value at line {}", .{line_num}); + } + } + } + return self; +} + +pub fn deinit(self: Self) void { + for (self.sections.items) |section| { + section.deinit(); + } + self.sections.deinit(); +} + +test "try parsing" { + const ini_str = + \\; Device level parameters + \\; UseTheseDomainSizes - When enabled (=1), use the sizes defined in the INI + \\; to defined the memory sizes for each domain + \\; When disabled (=0), for STAT_PLC, use the following + \\; defaults: + \\; Max Coils : 32768 elements + \\; Max Input Status: 32768 elements + \\; Max Input Regs: 16384 elements + \\; Max Holding Regs: 16384 elements + \\; All other memory types are 0 elements + \\;; For all other device models, the device communication + \\; interface will attempt to size the memory + \\; + \\; The default value is 0 + \\; + \\; UseCounts - When enabled (=1), indicates sizes are in elements + \\; When disabled (=0) indicates sizes are in bytes + \\; + \\; Default value is 0 + \\; + \\; ConservesConn - When enabled (=1), indicates that it is normal for the device + \\; to close the connection (typically based on inactivity). The + \\; device communication interface will not assume that the + \\; device is down unless it is unable to create a connection and + \\; get a response when it attempts the current scheduled + \\; operation to retrieve data from the device or modify data. + \\; + \\; When disabled (=0), indicates that a termination of the + \\; connection between the device communication interface and + \\; the device will cause the device communication interface to + \\; assume that the connection is down and terminate the + \\; connection + \\; + \\; Default value is 0. + \\; + \\; ConnSecondary - When Enabled (=1), in a Host Redundant environment, + \\; the device communciation interface will attempt to + \\; maintain a connection with the device on the acting + \\; secondary. + \\; + \\; When Disabled(=0), in a Host Redundant environment, + \\; the device communication interface will terminate its + \\; connection to the device when transitioning to the + \\; secondary. + \\; + \\; Default value is 1. + \\; + \\; OneCoilWrite - When enabled (=1) use Function 5 to write single coils + \\; When disabled (=0) use Function 15 to write single coils + \\; + \\; VersaMax ENIU, VersaPoint ENIU and Modicon 484's ignore this + \\; parameter. + \\; + \\;OneRegiserWrite - When enabled (=1) use Function 6 to write single holding registers + \\; When disabled (=0) use Function 16 to write single holding registers + \\; + \\; VersaMax ENIU, VersaPoint ENIU and Modicon 484's ignore this + \\; parameter. + \\; + \\[DEVICE1] + \\UseTheseDomainSizes=1 + \\UseCounts=0 + \\OneCoilWrite=0 + \\OneRegWrite=0 + \\ConservesConn=1 + \\ConnSecondary=0 + \\COILS=65535 + \\DISC INPUTS=65535 + \\INPUT REG.=65535 + \\HOLDING REG.=65535 + \\GEN REF FILE1=0 + \\GEN REF FILE2=0 + \\GEN REF FILE3=0 + \\GEN REF FILE4=0 + \\GEN REF FILE5=0 + \\GEN REF FILE6=0 + \\GEN REF FILE7=0 + \\GEN REF FILE8=0 + \\GEN REF FILE9=0 + \\GEN REF FILE10=0 + \\DP_INPUT REG.=0 + \\DP_HOLDING REG.=0 + \\ + \\[DEVICE2] + \\UseTheseDomainSizes=1 + \\UseCounts=0 + \\OneCoilWrite=0 + \\OneRegWrite=0 + \\ConservesConn=1 + \\ConnSecondary=0 + \\COILS=65535 + \\DISC INPUTS=65535 + \\INPUT REG.=65535 + \\HOLDING REG.=65535 + \\GEN REF FILE1=0 + \\GEN REF FILE2=0 + \\GEN REF FILE3=0 + \\GEN REF FILE4=0 + \\GEN REF FILE5=0 + \\GEN REF FILE6=0 + \\GEN REF FILE7=0 + \\GEN REF FILE8=0 + \\GEN REF FILE9=0 + \\GEN REF FILE10=0 + \\DP_INPUT REG.=0 + \\DP_HOLDING REG.=0 + ; + const ini = try parse(testing.allocator, ini_str); + defer ini.deinit(); + try testing.expectEqual(@as(usize, 2), ini.sections.items.len); + try testing.expectEqualStrings("DEVICE1", ini.sections.items[0].name); + try testing.expectEqualStrings("DEVICE2", ini.sections.items[1].name); + try testing.expectEqual(@as(usize, 22), ini.sections.items[0].key_values.items.len); + try testing.expectEqualStrings("OneCoilWrite", ini.sections.items[0].key_values.items[2].key); + try testing.expectEqualStrings("0", ini.sections.items[0].key_values.items[2].value); +}