1 // parse the argument list
2 // return error message or NULL
5 // 's': string (char *)
6 // 'i': integer (int *)
7 // 'b': boolean (int *)
8 // '|': optional arguments follows
9 // '.': stop parsing, ignore rest
10 // 'R': stop parsing, set rest ptr (char *)
11 // string modifiers (also for 'R'):
12 // '!' -- don't allow empty strings
18 // WARNING! `line` will be modified!
19 // WARNING! string pointers will point INSIDE `line`, so don't discard it!
21 const char *iniParseArguments (char *line
, const char *fmt
, ...) {
25 if (line
== NULL
) return "alas";
29 char spec
= *fmt
++, *args
;
31 if (spec
== '|') { inOptional
= 1; continue; }
32 if (spec
== '.') { va_end(ap
); return NULL
; }
34 while (*line
&& isspace(*line
)) ++line
;
35 if (*line
== '#') *line
= 0;
38 char **p
= va_arg(ap
, char **);
42 if (*fmt
== '!') { ++fmt
; noempty
= 1; }
43 else if (*fmt
== '-') { ++fmt
; trimstr(line
); }
47 if (noempty
&& !line
[0]) return "invalid empty arg";
48 if (p
!= NULL
) *p
= line
;
53 // end of line, stop right here
55 if (!inOptional
) return "out of args";
61 char *dest
= args
, qch
= '#';
64 if (line
[0] == '"' || line
[0] == '\'') qch
= *line
++;
66 while (*line
&& *line
!= qch
) {
67 if (qch
== '#' && isspace(*line
)) break;
70 if (!line
[1]) { va_end(ap
); return "invalid escape"; }
72 case 'n': *dest
++ = '\n'; ++line
; break;
73 case 'r': *dest
++ = '\r'; ++line
; break;
74 case 't': *dest
++ = '\t'; ++line
; break;
75 case 'a': *dest
++ = '\a'; ++line
; break;
76 case 'e': *dest
++ = '\x1b'; ++line
; break; // esc
77 case 's': *dest
++ = ' '; ++line
; break;
80 if (!isxdigit(*line
)) { va_end(ap
); return "invalid hex escape"; }
81 n
= toupper(*line
)-'0'; if (n
> 9) n
-= 7;
83 if (isxdigit(*line
)) {
84 int b
= toupper(*line
)-'0'; if (b
> 9) b
-= 7;
93 for (int f
= 0; f
< 4; ++f
) {
94 if (*line
< '0' || *line
> '7') break;
95 n
= (n
*8)+(line
[0]-'0');
96 if (n
> 255) { va_end(ap
); return "invalid oct escape"; }
99 if (n
== 0) { va_end(ap
); return "invalid oct escape"; }
102 case '1'...'9': // decimal
104 for (int f
= 0; f
< 3; ++f
) {
105 if (*line
< '0' || *line
> '9') break;
106 n
= (n
*8)+(line
[0]-'0');
107 if (n
> 255) { va_end(ap
); return "invalid dec escape"; }
110 if (n
== 0) { va_end(ap
); return "invalid oct escape"; }
122 if (*line
!= qch
) return "unfinished string";
124 } else if (*line
&& *line
!= '#') ++line
;
127 // now process and convert argument
131 case 's': { /* string */
132 int noempty
= 0, trim
= 0;
136 if (*fmt
== '!') { noempty
= 1; ++fmt
; }
137 else if (*fmt
== '-') { trim
= 1; ++fmt
; }
141 if (trim
) trimstr(args
);
143 if (noempty
&& !args
[0]) { va_end(ap
); return "invalid empty string"; }
144 p
= va_arg(ap
, char **);
145 if (p
!= NULL
) *p
= args
;
150 return "invalid integer";
152 int *p
= va_arg(ap
, int *);
157 n
= strtol(args
, &eptr
, 0);
158 if (*eptr
) { va_end(ap
); return "invalid integer"; }
162 int minmax
[2], haveminmax
[2];
164 haveminmax
[0] = haveminmax
[1] = 0;
165 minmax
[0] = minmax
[1] = 0;
167 for (int f
= 0; f
< 2; ++f
) {
168 if (isdigit(*fmt
) || *fmt
== '-' || *fmt
== '+') {
171 if (*fmt
== '-') neg
= 1;
172 if (!isdigit(*fmt
)) {
174 if (!isdigit(*fmt
)) { va_end(ap
); return "invalid integer bounds"; }
176 while (isdigit(*fmt
)) {
177 minmax
[f
] = minmax
[f
]*10+(fmt
[0]-'0');
180 if (neg
) minmax
[f
] = -minmax
[f
];
181 //fprintf(stderr, "got: %d\n", minmax[f]);
184 if (f
== 1) { va_end(ap
); return "invalid integer bounds: extra comma"; }
185 // do nothing, we are happy
187 } else if (*fmt
== '}') {
190 } else { va_end(ap
); return "invalid integer bounds"; }
192 if (*fmt
!= '}') { va_end(ap
); return "invalid integer bounds"; }
195 //fprintf(stderr, "b: (%d,%d) (%d,%d)\n", haveminmax[0], minmax[0], haveminmax[1], minmax[1]);
196 if ((haveminmax
[0] && n
< minmax
[0]) || (haveminmax
[1] && n
> minmax
[1])) { va_end(ap
); return "integer out of bounds"; }
202 case 'b': { /* bool */
203 int *p
= va_arg(ap
, int *);
206 if (!args
[0]) { va_end(ap
); return "invalid boolean"; }
207 if (strcasecmp(args
, "true") == 0 || strcasecmp(args
, "on") == 0 ||
208 strcasecmp(args
, "tan") == 0 || strcasecmp(args
, "1") == 0 ||
209 strcasecmp(args
, "yes") == 0) {
211 } else if (strcasecmp(args
, "false") == 0 || strcasecmp(args
, "off") == 0 ||
212 strcasecmp(args
, "ona") == 0 || strcasecmp(args
, "0") == 0 ||
213 strcasecmp(args
, "no") == 0) {
217 return "invalid boolean";
222 return "invalid format specifier";
226 while (*line
&& isspace(*line
)) ++line
;
227 if (!line
[0] || line
[0] == '#') return NULL
;