-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patheini.c
More file actions
491 lines (437 loc) · 16.7 KB
/
eini.c
File metadata and controls
491 lines (437 loc) · 16.7 KB
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
// eINI (implementation)
#include <alloca.h>
#include <errno.h>
#include <libgen.h>
#include <regex.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <wchar.h>
#include <wctype.h>
#include "eini.h"
//
// Types
//
// A range
typedef struct {
unsigned beg; // beginning
unsigned end; // end
} range_t;
//
// Global variables
//
regex_t eini_re_include, eini_re_section, eini_re_value;
//
// Helper functions and macros
//
// Helper of `wsrc_strip` and `eini_parse()`. Set the `type`, `key`, and `value`
// fields of `ret` to `mytype`, `mykey`, and `myvalue` respectively.
#define set_ret(mytype, mykey, myvalue) \
ret.type = mytype; \
if (NULL == mykey) \
ret.key = NULL; \
else { \
wcslcpy(ret_key, NULL != mykey ? mykey : L"", EINI_SHORT); \
ret.key = ret_key; \
} \
if (NULL == myvalue) \
ret.value = NULL; \
else { \
wcslcpy(ret_value, NULL != myvalue ? myvalue : L"", EINI_LONG); \
wunescape(ret_value); \
ret.value = ret_value; \
}
// Helper of `eini_parse()`. Strip trailing whitespace from `wsrc`. If it's
// surrounded by single or double quotes, strip those as well. Return any syntax
// errors pertaining to non-terminated quotes.
#define wsrc_strip \
wlen = wmargtrim(wsrc, NULL); \
if (L'"' == wsrc[0]) { \
/* wsrc begins with a `"` */ \
if (wlen < 2) { \
/* wsrc equals `"`; accept it as it is */ \
} else if (L'"' == wsrc[wlen - 1]) { \
/* wsrc ends in '"' */ \
if (wescaped(wsrc, wlen - 1)) { \
/* the ending `"` is escaped; reject it */ \
set_ret(EINI_ERROR, NULL, L"Non-terminated quote"); \
return ret; \
} else { \
/* the ending `"` is not escaped; remove the `"`s and accept it */ \
wsrc[wlen - 1] = L'\0'; \
wsrc = &wsrc[1]; \
} \
} else { \
/* wsrc does not end in `"`; reject it */ \
set_ret(EINI_ERROR, NULL, L"Non-terminated quote"); \
return ret; \
} \
} else if (L'\'' == wsrc[0]) { \
/* wsrc begins with a `'` */ \
if (wlen < 2) { \
/* wsrc equals `'`; accept it as it is */ \
} else if (L'\'' == wsrc[wlen - 1]) { \
/* wsrc ends in `'` */ \
if (wescaped(wsrc, wlen - 1)) { \
/* the ending `'` is escaped; reject it */ \
set_ret(EINI_ERROR, NULL, L"Non-terminated quote"); \
return ret; \
} else { \
/* the ending `"` is not escaped; remove the `"`s and accept it */ \
wsrc[wlen - 1] = L'\0'; \
wsrc = &wsrc[1]; \
} \
} else { \
/* wsrc does not end in `'`; reject it */ \
set_ret(EINI_ERROR, NULL, L"Non-terminated quote"); \
return ret; \
} \
}
// Helper of `populate_ipath` and `eini()`. Call `ef(errmsg, path, i)`, wind
// down, and return.
#define call_ef_and_return \
ef(errmsg, path, i); \
if (NULL != fp) \
fclose(fp); \
return;
// Helper of `eini()`, called when handling an inclusion. Populate `ipath` with
// the correct path of the included file. In case of error (such as file not
// found), call `em()` and return.
#define populate_ipath \
if (L'/' == lne.value[0]) { \
/* Absolute path */ \
if (-1 == wcstombs(ipath, lne.value, EINI_LONG)) { \
wcslcpy(errmsg, L"wcstombs() failed", EINI_LONG); \
call_ef_and_return; \
} \
} else { \
/* Relative path */ \
char svalue[EINI_LONG]; \
if (-1 == wcstombs(svalue, lne.value, EINI_LONG)) { \
wcslcpy(errmsg, L"wcstombs() failed", EINI_LONG); \
call_ef_and_return; \
} \
strlcpy(ipath, path, EINI_LONG); \
strlcpy(ipath, dirname(ipath), EINI_LONG); \
strlcat(ipath, "/", EINI_LONG); \
strlcat(ipath, svalue, EINI_LONG); \
} \
/* Error out if file was not found */ \
ifp = fopen(ipath, "r"); \
if (NULL == ifp) { \
swprintf(errmsg, EINI_LONG, L"Unable to open '%s'", ipath); \
call_ef_and_return; \
} \
fclose(ifp);
// Helper of `wsrc_strip()` and `decomment()`, i.e. `eini_parse()` ultimately.
// Test whether the character in `src[pos]` is escaped.
bool wescaped(wchar_t *src, unsigned pos) {
int c = 0; // number of '\'s before `pos`
int i = pos; // iterator
while (i > 0) {
i--;
if (L'\\' == src[i])
c++;
else
break;
}
return c % 2;
}
// Helper of `set_ret()`, i.e. `eini_parse()` ultimately. Unescape the string in
// `src`. `\a`, `\b`, `\t`, `\n`, `\v`, `\f`, and `\r` are unescaped into
// character codes 7 to 13, `\e` to character code 27 (ESC), `\\` to `\`, `\"`
// to `"`, and `\'` to `'`. All other escaped characters are discarded.
void wunescape(wchar_t *src) {
int i = 0, j = 0; // iterators
while (L'\0' != src[i]) {
if (L'\\' == src[i]) {
switch (src[i + 1]) {
case L'a':
src[j++] = L'\a';
break;
case L'b':
src[j++] = L'\b';
break;
case L't':
src[j++] = L'\t';
break;
case L'n':
src[j++] = L'\n';
break;
case L'v':
src[j++] = L'\v';
break;
case L'f':
src[j++] = L'\f';
break;
case L'r':
src[j++] = L'\r';
break;
case L'e':
src[j++] = L'\e';
break;
case L'\\':
src[j++] = L'\\';
break;
case L'"':
src[j++] = L'"';
break;
case L'\'':
src[j++] = L'\'';
break;
}
i += 2;
} else {
src[j++] = src[i];
i++;
}
}
src[j] = L'\0';
}
// Helper of `eini_parse()`. Return the position of the first character in `src`
// that is not whitespace, and not one of the characters in `extras`. (If
// `extras` is NULL then it is ignored.)
unsigned wmargend(const wchar_t *src, const wchar_t *extras) {
bool ws; // whether current character is whitespace or in extras
unsigned i, j; // iterators
if (NULL == extras)
extras = L"";
for (i = 0; L'\0' != src[i]; i++) {
ws = false;
if (iswspace(src[i]))
ws = true;
for (j = 0; L'\0' != extras[j]; j++)
if (src[i] == extras[j])
ws = true;
if (!ws)
return i;
}
return 0;
}
// Helper of `wsrc_strip` and `eini_parse()`. Trim all characters at the end of
// `trgt` that are either whitespace or one of the charactes in `extras`. (If
// `extras` is NULL then it is ignored.) Trimming is done by inserting 0 or more
// NULL characters at the end of `trgt`. Return the new length of `trgt`.
unsigned wmargtrim(wchar_t *trgt, const wchar_t *extras) {
int i; // iterator
unsigned j; // iterator
bool trim; // true if we'll be trimming `trgt[i]`
if (NULL == extras)
extras = L"";
for (i = 0; L'\0' != trgt[i]; i++)
;
i--;
while (i >= 0) {
trim = false;
if (iswspace(trgt[i]))
trim = true;
else
for (j = 0; L'\0' != extras[j]; j++)
if (trgt[i] == extras[j])
trim = true;
if (!trim) {
trgt[i + 1] = L'\0';
return i + 1;
}
i--;
}
trgt[0] = L'\0';
return 0;
}
// Helper of `eini_parse()`. Discard all comments in `src`.
void decomment(wchar_t *src) {
unsigned i = 0; // iterator
bool inq = false; // true if `src[i]` is inside a quoted string
wchar_t qtype = L'?'; // quote type: `'` or `"`
while (L'\0' != src[i]) {
if (inq) {
// `src[i]` is inside a quoted string
if (qtype == src[i] && !wescaped(src, i)) {
// `src[i]` is an unescaped `"` or `'`, thus the quoted string ends
inq = false;
}
} else {
// `src[i]` isn't inside a quoted string
if (L';' == src[i]) {
// `src[i]` is `;`, thus we have a comment to the end of line
src[i] = L'\0';
return;
} else if (L'"' == src[i] && !wescaped(src, i)) {
// `src[i]` is an unescaped `"`, thus a double-quoted string begins
inq = true;
qtype = L'"';
} else if (L'\'' == src[i] && !wescaped(src, i)) {
// src[i] is an unescaped `'`, thus a single-quoted string begins
inq = true;
qtype = L'\'';
}
}
i++;
}
}
// Helper of `eini_parse()`. Find the first match of `re` in `src` and return
// its location.
range_t match(regex_t re, char *src) {
regmatch_t pmatch[1]; // regex match
range_t res; // return value
// Try to match `src`
int err = regexec(&re, src, 1, pmatch, 0);
if (0 == err) {
// A match was found
res.beg = pmatch[0].rm_so;
res.end = pmatch[0].rm_eo;
} else {
// No match found, or an error has occured; return `{0, 0}`
res.beg = 0;
res.end = 0;
}
return res;
}
//
// Functions
//
void eini_init() {
regcomp(&eini_re_include, "[[:space:]]*include[[:space:]]*", REG_EXTENDED);
regcomp(&eini_re_section,
"[[:space:]]*\\[[[:space:]]*[a-zA-Z][a-zA-Z0-9_]*[[:space:]]*\\][[:"
"space:]]*",
REG_EXTENDED);
regcomp(&eini_re_value,
"[[:space:]]*[a-zA-Z][a-zA-Z0-9_]*[[:space:]]*=[[:space:]]*",
REG_EXTENDED);
}
eini_t eini_parse(char *src) {
wchar_t *wsrc =
alloca((EINI_LONG + 1) * sizeof(wchar_t)); // `wchar_t*` version of `src`
char *csrc = alloca(
(EINI_LONG + 1) *
sizeof(char)); // `char*` version of `src` (after removing comments)
int wlen; // length of `wsrc`
range_t loc; // location of regex match in `wsrc`
eini_t ret; // return value
static wchar_t ret_key[EINI_SHORT]; // `key` contents of `ret`
static wchar_t ret_value[EINI_LONG]; // `value` contents of `ret`
wlen = mbstowcs(wsrc, src, EINI_LONG);
if (-1 == wlen) {
// Couldn't convert `src`
set_ret(EINI_ERROR, NULL, L"Non-string data");
return ret;
}
wsrc[EINI_LONG - 1] = L'\0';
decomment(wsrc);
wcstombs(csrc, wsrc, EINI_LONG);
loc = match(eini_re_include, csrc);
if (0 == loc.beg && loc.end > loc.beg) {
// Include directive
wsrc = &wsrc[loc.end];
wsrc_strip;
set_ret(EINI_INCLUDE, NULL, wsrc);
return ret;
}
loc = match(eini_re_section, csrc);
if (!(0 == loc.beg && 0 == loc.end)) {
// Section
wsrc = &wsrc[wmargend(wsrc, L"[")];
wmargtrim(wsrc, L"]");
set_ret(EINI_SECTION, NULL, wsrc);
return ret;
}
loc = match(eini_re_value, csrc);
if (!(0 == loc.beg && 0 == loc.end)) {
// Key/value pair
wchar_t *wkey = wsrc;
wkey[loc.end - 1] = L'\0';
wkey = &wkey[wmargend(wkey, NULL)];
wmargtrim(wkey, L"=");
wsrc = &wsrc[loc.end];
wsrc_strip;
set_ret(EINI_VALUE, wkey, wsrc);
return ret;
}
wlen = wmargtrim(wsrc, NULL);
if (0 == wlen) {
// Empty line
set_ret(EINI_NONE, NULL, NULL);
return ret;
}
// None of the above
wchar_t errmsg[EINI_LONG];
swprintf(errmsg, EINI_LONG, L"Unable to parse '%ls'", wsrc);
set_ret(EINI_ERROR, NULL, errmsg);
return ret;
}
void eini(eini_handler_t hf, eini_error_t ef, const char *path) {
unsigned i = 0; // current line number in .ini file
FILE *fp = NULL; // file pointer for .ini file
char ln[EINI_LONG]; // current .ini file line text
eini_t lne; // current .ini file line parsed contents
wchar_t sec[EINI_SHORT] = L""; // current section
wchar_t errmsg[EINI_LONG]; // error message
fp = fopen(path, "r");
if (NULL == fp) {
swprintf(errmsg, EINI_LONG, L"Unable to open '%s'", path);
call_ef_and_return;
}
// If this is an empty file, do nothing
fseek(fp, 0, SEEK_END);
if (0 == ftell(fp))
return;
rewind(fp);
while (!feof(fp)) {
// Read next line into `ln`, and parse it into `lne`
if (NULL == fgets(ln, EINI_LONG, fp)) {
if (feof(fp))
break;
else {
wcslcpy(errmsg, L"Unable to read line", EINI_LONG);
printf("%s", strerror(errno));
call_ef_and_return;
}
}
i++;
lne = eini_parse(ln);
// Perform different actions, epending on what we got
switch (lne.type) {
case EINI_INCLUDE: {
// Call `eini()` to parse included .ini file
char ipath[EINI_LONG]; // included file path
FILE *ifp; // included file pointer
populate_ipath;
eini(hf, ef, ipath);
break;
}
case EINI_SECTION: {
// Populate `sec`
wcslcpy(sec, lne.value, EINI_SHORT);
break;
}
case EINI_VALUE: {
// Call `hf()` (but if `sec` hasn't been populated yet, call `ef()`)
if (0 == wcslen(sec)) {
swprintf(errmsg, EINI_LONG, L"Option '%ls' does not have a section",
lne.key);
call_ef_and_return;
} else
hf(sec, lne.key, lne.value, path, i);
break;
}
case EINI_ERROR: {
// Call `ef()`
wcslcpy(errmsg, lne.value, EINI_LONG);
call_ef_and_return;
break;
}
case EINI_NONE:
default:
break;
}
}
fclose(fp);
}
void eini_winddown() {
regfree(&eini_re_include);
regfree(&eini_re_section);
regfree(&eini_re_value);
}