build: Activate ONESHELL.
[lopsub.git] / lopsubgen.l
1 /*
2 * Copyright (C) 2016 Andre Noll <maan@tuebingen.mpg.de>
3 *
4 * Licensed under the GPL v3, see http://www.gnu.org/licenses/gpl-3.0.html
5 */
6
7 %option stack
8 %option never-interactive
9 %option yylineno
10
11 %s SC_COMMAND
12 %s SC_OPTION
13 %s SC_ARG_INFO
14 %s SC_ARG_TYPE
15 %s SC_OPTION_FLAG
16 %s SC_CHAR
17 %s SC_INTEGER
18 %s SC_UNQUOTED_LINE
19 %s SC_UNQUOTED_LINE_CHECK_DEFAULT
20 %s SC_ERROR
21 %s SC_VALUES_ID
22 %s SC_VALUES_COMMA_OR_BRACE
23
24 %x SC_INTRODUCTION
25 %x SC_DESCRIPTION
26 %x SC_CLOSING
27 %x SC_HELP
28 %x SC_CONCLUSION
29 %x SC_SECTION
30 %{
31 #include <ctype.h>
32 #include <stdlib.h>
33 #include <assert.h>
34 #include <stdbool.h>
35
36 #include "lsg.h"
37
38 static char **charpp;
39 static char *text_buf;
40 static size_t text_buf_len;
41 struct lsg_suite suite;
42
43 #define CURCMD (suite.commands[suite.num_subcommands])
44 #define CUROPT (CURCMD.options[CURCMD.num_options - 1])
45 #define CURSECT (suite.sections[suite.num_sections - 1])
46
47 static int yywrap(void) {return 1;}
48 static void *xmalloc(size_t size)
49 {
50 void *p;
51
52 assert(size > 0);
53 p = malloc(size);
54 assert(p);
55 return p;
56 }
57 void *xrealloc(void *ptr, size_t size)
58 {
59 void *p;
60 assert(size > 0);
61 p = realloc(ptr, size);
62 assert(p);
63 return p;
64 }
65
66 /*
67 * Extract ID from a string like [foo ID] or bar = ID, or a substring
68 * of a list: ID1, ID2, ...
69 */
70 static char *parse_identifier(char *p)
71 {
72 char *p2, *eq;
73
74 if (*p == '[') {
75 while (*p && isspace(*p))
76 p++;
77 while (*p && !isspace(*p))
78 p++;
79 } else {
80 if ((eq = strchr(p + 1, '=')))
81 p = eq + 1;
82 else
83 while (*p && (isspace(*p) || *p == ','))
84 p++;
85 }
86 assert(*p);
87 while (*p && isspace(*p))
88 p++;
89 assert(*p);
90 p2 = p;
91 while (isalnum(*p2) || *p2 == '_' || *p2 == '-')
92 p2++;
93 *p2 = '\0';
94 return strdup(p);
95 }
96
97 static char *parse_simple_string(void)
98 {
99 char *result, *p = yytext;
100 while (isspace(*p))
101 p++;
102 assert(*p == '[');
103 p++;
104 while (isspace(*p))
105 p++;
106 while (!isspace(*p)) /* skip "section" */
107 p++;
108 while (isspace(*p))
109 p++;
110 result = strdup(p);
111 p = strrchr(result, ']');
112 do {
113 *p = '\0';
114 p--;
115 } while (isspace(*p));
116 return result;
117 }
118 static void parse_id_string(void)
119 {
120 char *p, *q;
121 int val_num = CUROPT.num_values++, num_vals = val_num + 1;
122 bool backslash;
123
124 CUROPT.values = xrealloc(CUROPT.values,
125 num_vals * sizeof(char *));
126 CUROPT.value_ids = xrealloc(CUROPT.value_ids,
127 num_vals * sizeof(char *));
128 CUROPT.value_literals = xrealloc(CUROPT.value_literals,
129 num_vals * sizeof(char *));
130 CUROPT.value_ids[val_num] = p = strdup(yytext);
131 while (*p && !isspace(*p) && *p != '=')
132 p++;
133 *p = '\0';
134 p++;
135 while (*p && *p != '\"')
136 p++;
137 CUROPT.value_literals[val_num] = p;
138 p++;
139 CUROPT.values[val_num] = q = xmalloc(strlen(p) + 1);
140 for (backslash = false; *p; p++) {
141 if (*p == '\\') {
142 if (!backslash) {
143 backslash = true;
144 continue;
145 }
146 } else if (*p == '"') {
147 if (!backslash) {
148 *q = '\0';
149 return;
150 }
151 }
152 *q++ = *p;
153 backslash = false;
154 }
155 assert(false);
156 }
157 static char *parse_unquoted_line(void)
158 {
159 char *p = strdup(yytext);
160 size_t n = strlen(p);
161 for (; n > 0; n--) {
162 if (isspace(p[n - 1]))
163 continue;
164 p[n] = '\0';
165 break;
166 }
167 return p;
168 }
169 static void check_default_val(void)
170 {
171 int i;
172
173 if (!CUROPT.default_val)
174 return;
175 if (!CUROPT.values)
176 return;
177 for (i = 0; i < CUROPT.num_values; i++) {
178 char *val = CUROPT.values[i];
179 char buf[40];
180 if (strcmp(val, CUROPT.default_val))
181 continue;
182 sprintf(buf, "%i", i);
183 free(CUROPT.default_val);
184 CUROPT.default_val = strdup(buf);
185 return;
186 }
187 fprintf(stderr, "option %s: bad default value %s\n",
188 CUROPT.name.orig, CUROPT.default_val);
189 exit(EXIT_FAILURE);
190 }
191
192 %}
193 EQUALS [[:space:]]*=[[:space:]]*
194 IDENTIFIER [a-zA-Z]+[a-zA-Z0-9_-]*
195 C99_DECIMAL_CONSTANT -?([[:digit:]]{-}[0])[[:digit:]]*
196 C99_HEXADECIMAL_CONSTANT 0[xX][[:xdigit:]]+
197 C99_OCTAL_CONSTANT 0[01234567]*
198 INT_CONSTANT {C99_DECIMAL_CONSTANT}|{C99_HEXADECIMAL_CONSTANT}|{C99_OCTAL_CONSTANT}
199 STRING_VALUE \"([^\"\\\n]|(\\[\"\\]))*\"
200 SIMPLE_STRING [[:alnum:]]([[:alnum:]]|[[:space:]])*
201 %%
202
203 /* skip comments and whitespace */
204 ^[[:space:]]*#.*\n ;
205 [[:space:]]|\n+ ;
206
207 <INITIAL>\[[[:space:]]*suite[[:space:]]+{IDENTIFIER}[[:space:]]*\] {
208 free(suite.name.orig);
209 suite.name.orig = parse_identifier(yytext);
210 }
211
212 <INITIAL>caption{EQUALS} {
213 charpp = &suite.caption;
214 yy_push_state(SC_UNQUOTED_LINE);
215 }
216
217 <INITIAL>title{EQUALS} {
218 charpp = &suite.title;
219 yy_push_state(SC_UNQUOTED_LINE);
220 }
221
222 <INITIAL>mansect{EQUALS} {
223 charpp = &suite.mansect;
224 yy_push_state(SC_UNQUOTED_LINE);
225 }
226
227 <INITIAL>date{EQUALS} {
228 charpp = &suite.date;
229 yy_push_state(SC_UNQUOTED_LINE);
230 }
231
232 <INITIAL>version-string{EQUALS} {
233 charpp = &suite.version_string;
234 yy_push_state(SC_UNQUOTED_LINE);
235 }
236
237 <INITIAL>manual_title{EQUALS} {
238 charpp = &suite.manual_title;
239 yy_push_state(SC_UNQUOTED_LINE);
240 }
241
242 <INITIAL>aux_info_prefix{EQUALS} {
243 charpp = &suite.aux_info_prefix;
244 yy_push_state(SC_UNQUOTED_LINE);
245 }
246
247 <INITIAL>aux_info_default{EQUALS} {
248 charpp = &suite.aux_info_default;
249 yy_push_state(SC_UNQUOTED_LINE);
250 }
251
252 <INITIAL,SC_COMMAND,SC_OPTION>\[[[:space:]]*introduction[[:space:]]*\] {
253 text_buf = NULL;
254 text_buf_len = 0;
255 yy_push_state(SC_INTRODUCTION);
256 }
257
258 <SC_INTRODUCTION>[[:space:]]*\[[[:space:]]*\/introduction[[:space:]]*\]\n {
259 suite.introduction = text_buf;
260 yy_pop_state();
261 }
262
263 <INITIAL,SC_COMMAND,SC_OPTION>\[[[:space:]]*conclusion[[:space:]]*\] {
264 text_buf = NULL;
265 text_buf_len = 0;
266 yy_push_state(SC_CONCLUSION);
267 }
268
269 <SC_CONCLUSION>[[:space:]]*\[[[:space:]]*\/conclusion[[:space:]]*\]\n {
270 suite.conclusion = text_buf;
271 yy_pop_state();
272 }
273
274 <INITIAL>\[[[:space:]]*supercommand[[:space:]]+{IDENTIFIER}[[:space:]]*\] {
275 struct lsg_command *cmd;
276
277 if (!suite.commands)
278 suite.commands = xmalloc(sizeof(*suite.commands));
279 cmd = suite.commands;
280 memset(cmd, 0, sizeof(*cmd));
281 cmd->name.orig = parse_identifier(yytext);
282 cmd->options = xmalloc(sizeof(*cmd->options));
283 BEGIN(SC_COMMAND);
284 }
285
286 <INITIAL,SC_COMMAND,SC_OPTION>\[[[:space:]]*subcommand[[:space:]]+{IDENTIFIER}[[:space:]]*\] {
287 int command_num = ++suite.num_subcommands;
288 struct lsg_command *cmd;
289
290 suite.commands = realloc(suite.commands,
291 (suite.num_subcommands + 1) * sizeof(*suite.commands));
292 cmd = suite.commands + command_num;
293 memset(cmd, 0, sizeof(*cmd));
294 cmd->name.orig = parse_identifier(yytext);
295 cmd->options = xmalloc(sizeof(*cmd->options));
296 BEGIN(SC_COMMAND);
297 }
298
299 <SC_COMMAND>\[[[:space:]]*description[[:space:]]*\] {
300 text_buf = NULL;
301 text_buf_len = 0;
302 yy_push_state(SC_DESCRIPTION);
303 }
304
305 <SC_COMMAND,SC_OPTION>\[[[:space:]]*closing[[:space:]]*\] {
306 text_buf = NULL;
307 text_buf_len = 0;
308 yy_push_state(SC_CLOSING);
309 }
310
311 <SC_OPTION>\[[[:space:]]*help[[:space:]]*\] {
312 text_buf = NULL;
313 text_buf_len = 0;
314 yy_push_state(SC_HELP);
315 }
316 <INITIAL,SC_OPTION,SC_COMMAND>\[[[:space:]]*section[[:space:]]+{SIMPLE_STRING}[[:space:]]*\] {
317 int sect_num = suite.num_sections++;
318 suite.sections = realloc(suite.sections,
319 suite.num_sections * sizeof(*suite.sections));
320 CURSECT.name.orig = parse_simple_string();
321 text_buf = NULL;
322 text_buf_len = 0;
323 yy_push_state(SC_SECTION);
324 }
325
326 <SC_DESCRIPTION>[[:space:]]*\[[[:space:]]*\/description[[:space:]]*\]\n {
327 CURCMD.description = text_buf;
328 yy_pop_state();
329 }
330
331 <SC_CLOSING>[[:space:]]*\[[[:space:]]*\/closing[[:space:]]*\]\n {
332 CURCMD.closing = text_buf;
333 yy_pop_state();
334 }
335
336 <SC_HELP>[[:space:]]*\[[[:space:]]*\/help[[:space:]]*\]\n {
337 CUROPT.help = text_buf;
338 yy_pop_state();
339 }
340
341 <SC_SECTION>[[:space:]]*\[[[:space:]]*\/section[[:space:]]*\]\n {
342 CURSECT.text = text_buf;
343 yy_pop_state();
344 }
345
346 <SC_DESCRIPTION,SC_HELP,SC_SECTION,SC_CLOSING,SC_INTRODUCTION,SC_CONCLUSION>.*\n {
347 size_t new_len = text_buf_len + yyleng;
348 size_t num_tabs = 0;
349
350 while (yytext[num_tabs] == '\t')
351 num_tabs++;
352 new_len = text_buf_len + yyleng - num_tabs;
353 text_buf = realloc(text_buf, new_len + 1);
354 memcpy(text_buf + text_buf_len, yytext + num_tabs, yyleng - num_tabs);
355 text_buf[new_len] = '\0';
356 text_buf_len = new_len;
357 }
358
359 <SC_COMMAND,SC_OPTION>\[[[:space:]]*option[[:space:]]+{IDENTIFIER}[[:space:]]*\] {
360 int option_num = CURCMD.num_options++;
361 struct lsg_option *opt;
362
363 CURCMD.options = realloc(CURCMD.options,
364 CURCMD.num_options * sizeof(*CURCMD.options));
365 memset(&CUROPT, 0, sizeof(CUROPT));
366 CUROPT.name.orig = parse_identifier(yytext);
367 BEGIN(SC_OPTION);
368 }
369
370 <SC_OPTION>arg_info{EQUALS} BEGIN(SC_ARG_INFO);
371 <SC_ARG_INFO>no_arg CUROPT.arg_info = "LLS_NO_ARGUMENT"; BEGIN(SC_OPTION);
372 <SC_ARG_INFO>required_arg CUROPT.arg_info = "LLS_REQUIRED_ARGUMENT"; BEGIN(SC_OPTION);
373 <SC_ARG_INFO>optional_arg CUROPT.arg_info = "LLS_OPTIONAL_ARGUMENT"; BEGIN(SC_OPTION);
374
375 <SC_OPTION>arg_type{EQUALS} BEGIN(SC_ARG_TYPE);
376 <SC_ARG_TYPE>none CUROPT.arg_type = "LLS_NONE"; BEGIN(SC_OPTION);
377 <SC_ARG_TYPE>string CUROPT.arg_type = "LLS_STRING"; BEGIN(SC_OPTION);
378 <SC_ARG_TYPE>int32 CUROPT.arg_type = "LLS_INT32"; BEGIN(SC_OPTION);
379 <SC_ARG_TYPE>uint32 CUROPT.arg_type = "LLS_UINT32"; BEGIN(SC_OPTION);
380 <SC_ARG_TYPE>int64 CUROPT.arg_type = "LLS_INT64"; BEGIN(SC_OPTION);
381 <SC_ARG_TYPE>uint64 CUROPT.arg_type = "LLS_UINT64"; BEGIN(SC_OPTION);
382
383 <SC_OPTION>flag BEGIN(SC_OPTION_FLAG);
384 <SC_OPTION_FLAG>multiple CUROPT.multiple = true; BEGIN(SC_OPTION);
385 <SC_OPTION_FLAG>required CUROPT.required = true; BEGIN(SC_OPTION);
386 <SC_OPTION_FLAG>ignored CUROPT.ignored = true; BEGIN(SC_OPTION);
387
388 <SC_OPTION>default_val{EQUALS} {
389 charpp = &CUROPT.default_val;
390 if (!CUROPT.arg_type || strcmp(CUROPT.arg_type, "LLS_NONE") == 0) {
391 fprintf(stderr, "default_value for option w/o arguments!?\n");
392 exit(1);
393 } else if (strcmp(CUROPT.arg_type, "LLS_STRING") == 0)
394 yy_push_state(SC_UNQUOTED_LINE_CHECK_DEFAULT);
395 else
396 yy_push_state(SC_INTEGER);
397 }
398 <SC_OPTION>short_opt{EQUALS} BEGIN(SC_CHAR);
399 <SC_CHAR>[a-zA-Z0-9] CUROPT.short_opt = yytext[0]; BEGIN(SC_OPTION);
400
401 <SC_OPTION>values{EQUALS}\{ {
402 if (!CUROPT.arg_type || strcmp(CUROPT.arg_type, "LLS_STRING")) {
403 fprintf(stderr, "value list is only supported for string options\n");
404 exit(EXIT_FAILURE);
405 }
406 if (!CUROPT.arg_info || !strcmp(CUROPT.arg_info, "LLS_NO_ARGUMENT")) {
407 fprintf(stderr, "enum options must take an argument\n");
408 exit(EXIT_FAILURE);
409 }
410 if (CUROPT.default_val) {
411 fprintf(stderr, "value list must preceed default value\n");
412 exit(EXIT_FAILURE);
413 }
414 BEGIN(SC_VALUES_ID);
415 }
416
417 <SC_VALUES_ID>{IDENTIFIER}{EQUALS}{STRING_VALUE} {
418 parse_id_string();
419 BEGIN(SC_VALUES_COMMA_OR_BRACE);
420 }
421
422 <SC_VALUES_COMMA_OR_BRACE>[,\}] {
423 if (*yytext == ',')
424 BEGIN(SC_VALUES_ID);
425 else {
426 check_default_val();
427 BEGIN(SC_OPTION);
428 }
429 }
430
431 <SC_OPTION>summary{EQUALS} {
432 charpp = &CUROPT.summary;
433 yy_push_state(SC_UNQUOTED_LINE);
434 }
435 <SC_OPTION>typestr{EQUALS} {
436 charpp = &CUROPT.typestr;
437 yy_push_state(SC_UNQUOTED_LINE);
438 }
439
440 <SC_COMMAND>purpose{EQUALS} {
441 charpp = &CURCMD.purpose;
442 yy_push_state(SC_UNQUOTED_LINE);
443 }
444
445 <SC_COMMAND>non-opts-name{EQUALS} {
446 charpp = &CURCMD.non_opts_name;
447 yy_push_state(SC_UNQUOTED_LINE);
448 }
449
450 <SC_COMMAND>synopsis{EQUALS} {
451 charpp = &CURCMD.synopsis;
452 yy_push_state(SC_UNQUOTED_LINE);
453 }
454
455 <SC_COMMAND>aux_info{EQUALS} {
456 charpp = &CURCMD.aux_info;
457 yy_push_state(SC_UNQUOTED_LINE);
458 }
459
460 <SC_UNQUOTED_LINE>.*\n {
461 *charpp = parse_unquoted_line();
462 yy_pop_state();
463 }
464 <SC_UNQUOTED_LINE_CHECK_DEFAULT>.*\n {
465 *charpp = parse_unquoted_line();
466 check_default_val();
467 yy_pop_state();
468 }
469
470 <SC_INTEGER>{INT_CONSTANT} *charpp = strdup(yytext); yy_pop_state();
471
472 /* This rule runs iff none of the above patterns matched */
473 . {
474 fprintf(stderr, "parse error at line %d. Unmatched: \"%s\"\n",
475 yyget_lineno(), yytext);
476 BEGIN(SC_ERROR);
477 }
478 <SC_ERROR>.*\n {
479 fprintf(stderr, "subsequent unparsed input: %s\n", yytext);
480 exit(EXIT_FAILURE);
481 }