-
Notifications
You must be signed in to change notification settings - Fork 1
/
ini.zig
171 lines (167 loc) · 6.1 KB
/
ini.zig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
const std = @import("std");
// we ignore whitespace and comments
pub const Token = union(enum) {
comment,
section: []const u8,
key: []const u8,
value: []const u8
};
pub const State = enum {
normal, section, key, value, comment
};
pub fn getTok(data: []const u8, pos: *usize, state: *State) ?Token {
// if the position advances to the end of the data, there's no more tokens for us
if (pos.* >= data.len) return null;
var cur: u8 = 0;
// used for slicing
var start = pos.*;
var end = start;
while (cur != '\n') {
cur = data[ pos.* ];
pos.* += 1;
switch (state.*) {
.normal => {
switch (cur) {
'[' => {
state.* = .section;
start = pos.*;
end = start;
},
'=' => {
state.* = .value;
start = pos.*;
if (std.ascii.isSpace(data[start])) start += 1;
end = start;
},
';' => {
state.* = .comment;
},
// if it is whitespace itgets skipped over anyways
else => if (!std.ascii.isSpace(cur)) {
state.* = .key;
start = pos.* - 1;
end = start;
}
}
},
.section => {
end += 1;
switch (cur) {
']' => {
state.* = .normal;
pos.* += 1;
return Token { .section = data[start..end - 1] };
},
else => {}
}
},
.value => {
switch (cur) {
';' => {
state.* = .comment;
return Token { .value = data[start..end - 2] };
},
else => {
end += 1;
switch (cur) {
'\n' => {
state.* = .normal;
return Token { .value = data[start..end - 2] };
},
else => {}
}
}
}
},
.comment => {
end += 1;
switch (cur) {
'\n' => {
state.* = .normal;
return Token.comment;
},
else => {}
}
},
.key => {
end += 1;
if (!(std.ascii.isAlNum(cur) or cur == '_')) {
state.* = .normal;
return Token { .key = data[start..end] };
}
}
}
}
return null;
}
pub fn readToStruct(comptime T: type, data: []const u8) !T {
var namespace: []const u8 = "";
var pos: usize = 0;
var state: State = .normal;
var ret = std.mem.zeroes(T);
while (getTok(data, &pos, &state)) |tok| {
switch (tok) {
.comment => {},
.section => |ns| {
namespace = ns;
},
.key => |key| {
var next_tok = getTok(data, &pos, &state);
// if there's nothing just give a comment which is also a syntax error
switch (next_tok orelse .comment) {
.value => |value| {
// now we have the namespace, key, and value
// namespace and key are runtime values, so we need to loop the struct instead of using @field
inline for(std.meta.fields(T)) |ns_info| {
if (std.mem.eql(u8, ns_info.name, namespace)) {
// @field(ret, ns_info.name) contains the inner struct now
// loop over the fields of the inner struct, and check for key matches
inline for(std.meta.fields(@TypeOf(@field(ret, ns_info.name)))) |key_info| {
if (std.mem.eql(u8, key_info.name, key)) {
// now we have a key match, give it the value
const my_type = @TypeOf(@field(@field(ret, ns_info.name), key_info.name));
@field(@field(ret, ns_info.name), key_info.name) = try convert(my_type, value);
}
}
}
}
},
// after a key, a value must follow
else => return error.SyntaxError
}
},
// if we get a value with no key, that's a bit nonsense
.value => return error.SyntaxError
}
}
return ret;
}
// I'll add more later
const truthyAndFalsy = std.ComptimeStringMap(bool, .{
.{ "true", true },
.{ "false", false },
.{ "1", true },
.{ "0", false }
});
pub fn convert(comptime T: type, val: []const u8) !T {
return switch (@typeInfo(T)) {
.Int, .ComptimeInt => try std.fmt.parseInt(T, val, 0),
.Float, .ComptimeFloat => try std.fmt.parseFloat(T, val),
.Bool => truthyAndFalsy.get(val).?,
else => @as(T, val)
};
}
pub fn writeStruct(struct_value: anytype, writer: anytype) !void {
inline for (std.meta.fields(@TypeOf(struct_value))) |field| {
try writer.print("[{s}]\n", .{field.name});
const pairs = @field(struct_value, field.name);
inline for (std.meta.fields(@TypeOf(pairs))) |pair| {
const key_value = @field(pairs, pair.name);
const format = switch (@TypeOf(key_value)) {
[]const u8 => "{s}",
else => "{}"
};
try writer.print("{s} = " ++ format ++ "\n", .{pair.name, key_value});
}
}
}