Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ jobs:
- uses: actions/checkout@v3
- uses: mlugg/setup-zig@v2
with:
version: 0.15.0
version: 0.16.0
- run: zig build test
21 changes: 9 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,19 @@ This is a very simple ini-parser library that provides:
### Zig

```zig
pub fn main() !void {
const file = try std.fs.cwd().openFile("example.ini", .{});
defer file.close();

var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer if (gpa.deinit() != .ok) @panic("memory leaked");
pub fn main(init: std.process.Init) !void {
const io = init.io;
const file = try std.Io.Dir.cwd().openFile(io, "example.ini", .{});
defer file.close(io);

var read_buffer: [1024]u8 = undefined;
var file_reader = file.reader(&read_buffer);
var parser = ini.parse(gpa.allocator(), &file_reader.interface, ";#");
var file_reader = file.reader(io, &read_buffer);
var parser = ini.parse(init.gpa, &file_reader.interface, ";#");
defer parser.deinit();

var write_buffer: [1024]u8 = undefined;
var file_writer = std.fs.File.stdout().writer(&write_buffer);
var writer = &file_writer.interface;
var file_writer = std.Io.File.stdout().writer(io, &write_buffer);
const writer = &file_writer.interface;
defer writer.flush() catch @panic("Could not flush to stdout");

while (try parser.next()) |record| {
Expand All @@ -36,8 +34,7 @@ pub fn main() !void {
.enumeration => |value| try writer.print("{s}\n", .{value}),
}
}
}
```
}```

### C

Expand Down
40 changes: 30 additions & 10 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,29 @@ pub fn build(b: *std.Build) void {
.target = target,
});

const ini_c_header = b.addTranslateC(.{
.root_source_file = b.path("src/ini.h"),
.target = target,
.optimize = optimize,
});

const lib = b.addLibrary(.{
.name = "ini",
.root_module = b.createModule(.{
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
.imports = &.{
.{
.name = "c",
.module = ini_c_header.createModule(),
},
},
}),
});
lib.bundle_compiler_rt = true;
lib.addIncludePath(b.path("src"));
lib.linkLibC();
lib.root_module.addIncludePath(b.path("src"));
lib.installHeader(b.path("src/ini.h"), "ini.h");
b.installArtifact(lib);

Expand All @@ -30,19 +42,19 @@ pub fn build(b: *std.Build) void {
.root_module = b.createModule(.{
.optimize = optimize,
.target = target,
.link_libc = true,
}),
});
example_c.addCSourceFile(.{
example_c.root_module.addCSourceFile(.{
.file = b.path("example/example.c"),
.flags = &.{
"-Wall",
"-Wextra",
"-pedantic",
},
});
example_c.addIncludePath(b.path("src"));
example_c.linkLibrary(lib);
example_c.linkLibC();
example_c.root_module.addIncludePath(b.path("src"));
example_c.root_module.linkLibrary(lib);
example_step.dependOn(&b.addInstallArtifact(example_c, .{}).step);

const example_zig = b.addExecutable(.{
Expand All @@ -51,9 +63,11 @@ pub fn build(b: *std.Build) void {
.root_source_file = b.path("example/example.zig"),
.optimize = optimize,
.target = target,
.imports = &.{
.{ .name = "ini", .module = b.modules.get("ini").? },
},
}),
});
example_zig.root_module.addImport("ini", b.modules.get("ini").?);
example_step.dependOn(&b.addInstallArtifact(example_zig, .{}).step);

const test_step = b.step("test", "Run library tests");
Expand All @@ -71,10 +85,16 @@ pub fn build(b: *std.Build) void {
.root_source_file = b.path("src/lib-test.zig"),
.optimize = optimize,
.target = target,
.link_libc = true,
.imports = &.{
.{
.name = "c",
.module = ini_c_header.createModule(),
},
},
}),
});
binding_tests.addIncludePath(b.path("src"));
binding_tests.linkLibrary(lib);
binding_tests.linkLibC();
binding_tests.root_module.addIncludePath(b.path("src"));
binding_tests.root_module.linkLibrary(lib);
test_step.dependOn(&b.addRunArtifact(binding_tests).step);
}
2 changes: 1 addition & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
.name = .ini,
.fingerprint = 0x7757b668623d2460,
.version = "0.1.0",
.minimum_zig_version = "0.15.0",
.minimum_zig_version = "0.16.0",
.paths = .{
"LICENCE",
"README.md",
Expand Down
18 changes: 8 additions & 10 deletions example/example.zig
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
const std = @import("std");
const ini = @import("ini");

pub fn main() !void {
const file = try std.fs.cwd().openFile("example.ini", .{});
defer file.close();

var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer if (gpa.deinit() != .ok) @panic("memory leaked");
pub fn main(init: std.process.Init) !void {
const io = init.io;
const file = try std.Io.Dir.cwd().openFile(io, "example.ini", .{});
defer file.close(io);

var read_buffer: [1024]u8 = undefined;
var file_reader = file.reader(&read_buffer);
var parser = ini.parse(gpa.allocator(), &file_reader.interface, ";#");
var file_reader = file.reader(io, &read_buffer);
var parser = ini.parse(init.gpa, &file_reader.interface, ";#");
defer parser.deinit();

var write_buffer: [1024]u8 = undefined;
var file_writer = std.fs.File.stdout().writer(&write_buffer);
var writer = &file_writer.interface;
var file_writer = std.Io.File.stdout().writer(io, &write_buffer);
const writer = &file_writer.interface;
defer writer.flush() catch @panic("Could not flush to stdout");

while (try parser.next()) |record| {
Expand Down
25 changes: 14 additions & 11 deletions src/ini.zig
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,26 @@ pub const Parser = struct {
const Self = @This();

allocator: std.mem.Allocator,
line_buffer: std.array_list.Managed(u8),
reader: *std.io.Reader,
line_buffer: std.ArrayList(u8),
reader: *std.Io.Reader,
comment_characters: []const u8,

pub fn deinit(self: *Self) void {
self.line_buffer.deinit();
self.line_buffer.deinit(self.allocator);
self.* = undefined;
}

pub fn next(self: *Self) !?Record {
var write_buffer: [1024]u8 = undefined;
var old_writer_adapter = self.line_buffer.writer().adaptToNewApi(&write_buffer);
var writer = &old_writer_adapter.new_interface;
self.line_buffer.clearRetainingCapacity();
while (true) {
_ = try self.reader.streamDelimiterLimit(writer, '\n', .limited(4096));
try writer.flush();
{
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the Io.Writer.Allocating can be reused here as well, we don't have to use the ArrayList roundtrip here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in 68228a2

This avoids the ArrayList roundtrip in the parser loop.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please drop the ArrayList completely. We don't need it when we can use the Writer.Allocating in the first place.

It's just a deprecated pattern now

var list_writer = std.Io.Writer.Allocating.fromArrayList(self.allocator, &self.line_buffer);
defer self.line_buffer = list_writer.toArrayList();

_ = try self.reader.streamDelimiterLimit(&list_writer.writer, '\n', .limited(4096));
try list_writer.writer.flush();
}

const discarded = self.reader.discard(.limited(1)) catch |e| blk: {
switch (e) {
error.EndOfStream => {
Expand All @@ -67,7 +70,7 @@ pub const Parser = struct {
};
if (self.line_buffer.items.len == 0 and discarded == 0)
return null;
try self.line_buffer.append(0); // append guaranteed space for sentinel
try self.line_buffer.append(self.allocator, 0); // append guaranteed space for sentinel

var line: []const u8 = self.line_buffer.items;
var last_index: usize = 0;
Expand Down Expand Up @@ -122,10 +125,10 @@ pub const Parser = struct {
};

/// Returns a new parser that can read the ini structure
pub fn parse(allocator: std.mem.Allocator, reader: *std.io.Reader, comment_characters: []const u8) Parser {
pub fn parse(allocator: std.mem.Allocator, reader: *std.Io.Reader, comment_characters: []const u8) Parser {
return Parser{
.allocator = allocator,
.line_buffer = std.array_list.Managed(u8).init(allocator),
.line_buffer = .empty,
.reader = reader,
.comment_characters = comment_characters,
};
Expand Down
5 changes: 1 addition & 4 deletions src/lib-test.zig
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
const std = @import("std");

const c = @cImport({
@cInclude("ini.h");
});
const c = @import("c");

test "parser create/destroy" {
var buffer: c.ini_Parser = undefined;
Expand Down
70 changes: 38 additions & 32 deletions src/lib.zig
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
const std = @import("std");
const ini = @import("ini.zig");

const c = @cImport({
@cInclude("ini.h");
});
const c = @import("c");

const Record = extern struct {
type: Type,
Expand All @@ -29,12 +26,12 @@ const Record = extern struct {
};

const BufferParser = struct {
stream: std.io.Reader,
stream: std.Io.Reader,
parser: ini.Parser,
};

const FileParser = struct {
old_reader_adapter: CReader.Adapter,
reader: CReader,
parser: ini.Parser,
};

Expand Down Expand Up @@ -70,7 +67,7 @@ comptime {
export fn ini_create_buffer(parser: *IniParser, data: [*]const u8, data_length: usize, comment_characters: [*]const u8, comment_characters_length: usize) void {
parser.* = IniParser{
.buffer = .{
.stream = std.io.Reader.fixed(data[0..data_length]),
.stream = std.Io.Reader.fixed(data[0..data_length]),
.parser = undefined,
},
};
Expand All @@ -81,12 +78,12 @@ export fn ini_create_buffer(parser: *IniParser, data: [*]const u8, data_length:
export fn ini_create_file(parser: *IniParser, read_buffer: [*]u8, read_buffer_length: usize, file: *std.c.FILE, comment_characters: [*]const u8, comment_characters_length: usize) void {
parser.* = IniParser{
.file = .{
.old_reader_adapter = cReader(file).adaptToNewApi(read_buffer[0..read_buffer_length]),
.reader = CReader.init(file, read_buffer[0..read_buffer_length]),
.parser = undefined,
},
};

parser.file.parser = ini.parse(std.heap.c_allocator, &parser.file.old_reader_adapter.new_interface, comment_characters[0..comment_characters_length]);
parser.file.parser = ini.parse(std.heap.c_allocator, &parser.file.reader.interface, comment_characters[0..comment_characters_length]);
}

export fn ini_destroy(parser: *IniParser) void {
Expand All @@ -97,7 +94,7 @@ export fn ini_destroy(parser: *IniParser) void {
parser.* = undefined;
}

const ParseError = error{ OutOfMemory, StreamTooLong } || std.io.Reader.Error || std.io.Writer.Error;
const ParseError = error{ OutOfMemory, StreamTooLong } || std.Io.Reader.Error || std.Io.Writer.Error;

fn mapError(err: ParseError) IniError {
return switch (err) {
Expand Down Expand Up @@ -141,28 +138,37 @@ export fn ini_next(parser: *IniParser, record: *Record) IniError {
return .success;
}

const CReader = std.Io.GenericReader(*std.c.FILE, std.fs.File.ReadError, cReaderRead);
extern "c" fn feof(stream: *std.c.FILE) c_int;

fn cReader(c_file: *std.c.FILE) CReader {
return .{ .context = c_file };
}
const CReader = struct {
file: *std.c.FILE,
interface: std.Io.Reader,

fn cReaderRead(c_file: *std.c.FILE, bytes: []u8) std.fs.File.ReadError!usize {
const amt_read = std.c.fread(bytes.ptr, 1, bytes.len, c_file);
if (amt_read >= 0) return amt_read;
switch (@as(std.os.E, @enumFromInt(std.c._errno().*))) {
.SUCCESS => unreachable,
.INVAL => unreachable,
.FAULT => unreachable,
.AGAIN => unreachable, // this is a blocking API
.BADF => unreachable, // always a race condition
.DESTADDRREQ => unreachable, // connect was never called
.DQUOT => return error.DiskQuota,
.FBIG => return error.FileTooBig,
.IO => return error.InputOutput,
.NOSPC => return error.NoSpaceLeft,
.PERM => return error.AccessDenied,
.PIPE => return error.BrokenPipe,
else => |err| return std.os.unexpectedErrno(err),
fn init(file: *std.c.FILE, buffer: []u8) CReader {
return .{
.file = file,
.interface = .{
.vtable = &.{ .stream = stream },
.buffer = buffer,
.seek = 0,
.end = 0,
},
};
}
}

fn stream(r: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize {
const creader: *CReader = @alignCast(@fieldParentPtr("interface", r));

if (limit == .nothing) return 0;
const dest = limit.slice(try w.writableSliceGreedy(1));

const n = std.c.fread(dest.ptr, 1, dest.len, creader.file);
if (n > 0) {
w.advance(n);
return n;
}

if (feof(creader.file) != 0) return error.EndOfStream;
return error.ReadFailed;
}
};
Loading