Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,22 @@ This is a very simple ini-parser library that provides:
### Zig

```zig
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");
var parser = ini.parse(gpa.allocator(), file.reader(), ";#");

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

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

while (try parser.next()) |record| {
switch (record) {
Expand All @@ -45,12 +48,13 @@ pub fn main() !void {
#include <stdbool.h>

int main() {
FILE * f = fopen("example.ini", "rb");
FILE * f = fopen("example.ini", "r");
if(!f)
return 1;

struct ini_Parser parser;
ini_create_file(&parser, f, ";#", 2);
char read_buffer[1024] = {0};
ini_create_file(&parser, read_buffer, sizeof read_buffer, f, ";#", 2);

struct ini_Record record;
while(true)
Expand Down
2 changes: 2 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ pub fn build(b: *std.Build) void {

_ = b.addModule("ini", .{
.root_source_file = b.path("src/ini.zig"),
.optimize = optimize,
.target = target,
});

const lib = b.addLibrary(.{
Expand Down
5 changes: 3 additions & 2 deletions example/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
#include <stdbool.h>

int main() {
FILE * f = fopen("example.ini", "rb");
FILE * f = fopen("example.ini", "r");
if(!f)
return 1;

struct ini_Parser parser;
ini_create_file(&parser, f, ";#", 2);
char read_buffer[1024] = {0};
ini_create_file(&parser, read_buffer, sizeof read_buffer, f, ";#", 2);

struct ini_Record record;
while(true)
Expand Down
10 changes: 8 additions & 2 deletions example/example.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@ pub fn main() !void {

var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer if (gpa.deinit() != .ok) @panic("memory leaked");
var parser = ini.parse(gpa.allocator(), file.reader(), ";#");

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

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

while (try parser.next()) |record| {
switch (record) {
Expand Down
4 changes: 3 additions & 1 deletion src/ini.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
/// after being initialized with `ini_create_*`!
struct ini_Parser
{
alignas(16) char opaque[128];
alignas(16) char opaque[256];
};

enum ini_RecordType : int
Expand Down Expand Up @@ -56,6 +56,8 @@ extern void ini_create_buffer(

extern void ini_create_file(
struct ini_Parser * parser,
char * read_buffer,
size_t read_buffer_length,
FILE * file,
char const * comment_characters,
size_t comment_characters_length
Expand Down
137 changes: 74 additions & 63 deletions src/ini.zig
Original file line number Diff line number Diff line change
Expand Up @@ -34,85 +34,96 @@ fn insertNulTerminator(slice: []const u8) [:0]const u8 {
return mut_ptr[0..slice.len :0];
}

pub fn Parser(comptime Reader: type) type {
return struct {
const Self = @This();

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

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

pub fn next(self: *Self) !?Record {
while (true) {
self.reader.readUntilDelimiterArrayList(&self.line_buffer, '\n', 4096) catch |err| switch (err) {
pub const Parser = struct {
const Self = @This();

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

pub fn deinit(self: *Self) void {
self.line_buffer.deinit();
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();
const discarded = self.reader.discard(.limited(1)) catch |e| blk: {
switch (e) {
error.EndOfStream => {
if (self.line_buffer.items.len == 0)
return null;
break :blk 0;
},
else => |e| return e,
};
try self.line_buffer.append(0); // append guaranteed space for sentinel

var line: []const u8 = self.line_buffer.items;
var last_index: usize = 0;

// handle comments and escaping
while (last_index < line.len) {
if (std.mem.indexOfAnyPos(u8, line, last_index, self.comment_characters)) |index| {
// escape character if needed, then skip it (it's not a comment)
if (index > 0) {
const previous_index = index - 1;
const previous_char = line[previous_index];

if (previous_char == '\\') {
_ = self.line_buffer.orderedRemove(previous_index);
line = self.line_buffer.items;

last_index = index + 1;
continue;
}
else => return e,
}
};
if (self.line_buffer.items.len == 0 and discarded == 0)
return null;
try self.line_buffer.append(0); // append guaranteed space for sentinel

var line: []const u8 = self.line_buffer.items;
var last_index: usize = 0;

// handle comments and escaping
while (last_index < line.len) {
if (std.mem.indexOfAnyPos(u8, line, last_index, self.comment_characters)) |index| {
// escape character if needed, then skip it (it's not a comment)
if (index > 0) {
const previous_index = index - 1;
const previous_char = line[previous_index];

if (previous_char == '\\') {
_ = self.line_buffer.orderedRemove(previous_index);
line = self.line_buffer.items;

last_index = index + 1;
continue;
}

line = std.mem.trim(u8, line[0..index], whitespace);
} else {
line = std.mem.trim(u8, line, whitespace);
}

break;
line = std.mem.trim(u8, line[0..index], whitespace);
} else {
line = std.mem.trim(u8, line, whitespace);
}

if (line.len == 0)
continue;
break;
}

if (std.mem.startsWith(u8, line, "[") and std.mem.endsWith(u8, line, "]")) {
return Record{ .section = insertNulTerminator(line[1 .. line.len - 1]) };
}
if (line.len == 0) {
self.line_buffer.clearRetainingCapacity();
continue;
}

if (std.mem.indexOfScalar(u8, line, '=')) |index| {
return Record{
.property = KeyValue{
// note: the key *might* replace the '=' in the slice with 0!
.key = insertNulTerminator(std.mem.trim(u8, line[0..index], whitespace)),
.value = insertNulTerminator(std.mem.trim(u8, line[index + 1 ..], whitespace)),
},
};
}
if (std.mem.startsWith(u8, line, "[") and std.mem.endsWith(u8, line, "]")) {
return Record{ .section = insertNulTerminator(line[1 .. line.len - 1]) };
}

return Record{ .enumeration = insertNulTerminator(line) };
if (std.mem.indexOfScalar(u8, line, '=')) |index| {
return Record{
.property = KeyValue{
// note: the key *might* replace the '=' in the slice with 0!
.key = insertNulTerminator(std.mem.trim(u8, line[0..index], whitespace)),
.value = insertNulTerminator(std.mem.trim(u8, line[index + 1 ..], whitespace)),
},
};
}

return Record{ .enumeration = insertNulTerminator(line) };
}
};
}
}
};

/// Returns a new parser that can read the ini structure
pub fn parse(allocator: std.mem.Allocator, reader: anytype, comment_characters: []const u8) Parser(@TypeOf(reader)) {
return Parser(@TypeOf(reader)){
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),
.reader = reader,
Expand Down
3 changes: 2 additions & 1 deletion src/lib-test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ test "file parser" {
defer _ = c.fclose(file);

var parser: c.ini_Parser = undefined;
c.ini_create_file(&parser, file, ";#", 2);
var read_buffer: [1024]u8 = undefined;
c.ini_create_file(&parser, &read_buffer, read_buffer.len, file, ";#", 2);
defer c.ini_destroy(&parser);

try commonTest(&parser);
Expand Down
30 changes: 20 additions & 10 deletions src/lib.zig
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,18 @@ const Record = extern struct {
};

const BufferParser = struct {
stream: std.io.FixedBufferStream([]const u8),
parser: ini.Parser(std.io.FixedBufferStream([]const u8).Reader),
stream: std.io.Reader,
parser: ini.Parser,
};

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

const IniParser = union(enum) {
buffer: BufferParser,
file: ini.Parser(CReader),
file: FileParser,
};

const IniError = enum(c.ini_Error) {
Expand Down Expand Up @@ -65,29 +70,34 @@ 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.fixedBufferStream(data[0..data_length]),
.stream = std.io.Reader.fixed(data[0..data_length]),
.parser = undefined,
},
};
// this is required to have the parser store a pointer to the stream.
parser.buffer.parser = ini.parse(std.heap.c_allocator, parser.buffer.stream.reader(), comment_characters[0..comment_characters_length]);
parser.buffer.parser = ini.parse(std.heap.c_allocator, &parser.buffer.stream, comment_characters[0..comment_characters_length]);
}

export fn ini_create_file(parser: *IniParser, file: *std.c.FILE, comment_characters: [*]const u8, comment_characters_length: usize) void {
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 = ini.parse(std.heap.c_allocator, cReader(file), comment_characters[0..comment_characters_length]),
.file = .{
.old_reader_adapter = cReader(file).adaptToNewApi(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]);
}

export fn ini_destroy(parser: *IniParser) void {
switch (parser.*) {
.buffer => |*p| p.parser.deinit(),
.file => |*p| p.deinit(),
.file => |*p| p.parser.deinit(),
}
parser.* = undefined;
}

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

fn mapError(err: ParseError) IniError {
return switch (err) {
Expand All @@ -100,7 +110,7 @@ fn mapError(err: ParseError) IniError {
export fn ini_next(parser: *IniParser, record: *Record) IniError {
const src_record_or_null: ?ini.Record = switch (parser.*) {
.buffer => |*p| p.parser.next() catch |e| return mapError(e),
.file => |*p| p.next() catch |e| return mapError(e),
.file => |*p| p.parser.next() catch |e| return mapError(e),
};

if (src_record_or_null) |src_record| {
Expand Down
Loading