/* * Copyright (C) 2016 Andre Noll * * Licensed under the GPL v3, see http://www.gnu.org/licenses/gpl-3.0.html */ %option stack %option never-interactive %option yylineno %s SC_COMMAND %s SC_OPTION %s SC_ARG_INFO %s SC_ARG_TYPE %s SC_OPTION_FLAG %s SC_CHAR %s SC_INTEGER %s SC_UNQUOTED_LINE %s SC_UNQUOTED_LINE_CHECK_DEFAULT %s SC_ERROR %s SC_VALUES_ID %s SC_VALUES_COMMA_OR_BRACE %x SC_INTRODUCTION %x SC_DESCRIPTION %x SC_CLOSING %x SC_HELP %x SC_CONCLUSION %x SC_SECTION %{ #include #include #include #include #include "lsg.h" static char **charpp; static char *text_buf; static size_t text_buf_len; struct lsg_suite suite; #define CURCMD (suite.commands[suite.num_subcommands]) #define CUROPT (CURCMD.options[CURCMD.num_options - 1]) #define CURSECT (suite.sections[suite.num_sections - 1]) static int yywrap(void) {return 1;} static void *xmalloc(size_t size) { void *p; assert(size > 0); p = malloc(size); assert(p); return p; } void *xrealloc(void *ptr, size_t size) { void *p; assert(size > 0); p = realloc(ptr, size); assert(p); return p; } /* * Extract ID from a string like [foo ID] or bar = ID, or a substring * of a list: ID1, ID2, ... */ static char *parse_identifier(char *p) { char *p2, *eq; if (*p == '[') { while (*p && isspace(*p)) p++; while (*p && !isspace(*p)) p++; } else { if ((eq = strchr(p + 1, '='))) p = eq + 1; else while (*p && (isspace(*p) || *p == ',')) p++; } assert(*p); while (*p && isspace(*p)) p++; assert(*p); p2 = p; while (isalnum(*p2) || *p2 == '_' || *p2 == '-') p2++; *p2 = '\0'; return strdup(p); } static char *parse_simple_string(void) { char *result, *p = yytext; while (isspace(*p)) p++; assert(*p == '['); p++; while (isspace(*p)) p++; while (!isspace(*p)) /* skip "section" */ p++; while (isspace(*p)) p++; result = strdup(p); p = strrchr(result, ']'); do { *p = '\0'; p--; } while (isspace(*p)); return result; } static void parse_id_string(void) { char *p, *q; int val_num = CUROPT.num_values++, num_vals = val_num + 1; bool backslash; CUROPT.values = xrealloc(CUROPT.values, num_vals * sizeof(char *)); CUROPT.value_ids = xrealloc(CUROPT.value_ids, num_vals * sizeof(char *)); CUROPT.value_literals = xrealloc(CUROPT.value_literals, num_vals * sizeof(char *)); CUROPT.value_ids[val_num] = p = strdup(yytext); while (*p && !isspace(*p) && *p != '=') p++; *p = '\0'; p++; while (*p && *p != '\"') p++; CUROPT.value_literals[val_num] = p; p++; CUROPT.values[val_num] = q = xmalloc(strlen(p) + 1); for (backslash = false; *p; p++) { if (*p == '\\') { if (!backslash) { backslash = true; continue; } } else if (*p == '"') { if (!backslash) { *q = '\0'; return; } } *q++ = *p; backslash = false; } assert(false); } static char *parse_unquoted_line(void) { char *p = strdup(yytext); size_t n = strlen(p); for (; n > 0; n--) { if (isspace(p[n - 1])) continue; p[n] = '\0'; break; } return p; } static void check_default_val(void) { int i; if (!CUROPT.default_val) return; if (!CUROPT.values) return; for (i = 0; i < CUROPT.num_values; i++) { char *val = CUROPT.values[i]; char buf[40]; if (strcmp(val, CUROPT.default_val)) continue; sprintf(buf, "%i", i); free(CUROPT.default_val); CUROPT.default_val = strdup(buf); return; } fprintf(stderr, "option %s: bad default value %s\n", CUROPT.name.orig, CUROPT.default_val); exit(EXIT_FAILURE); } %} EQUALS [[:space:]]*=[[:space:]]* IDENTIFIER [a-zA-Z]+[a-zA-Z0-9_-]* C99_DECIMAL_CONSTANT -?([[:digit:]]{-}[0])[[:digit:]]* C99_HEXADECIMAL_CONSTANT 0[xX][[:xdigit:]]+ C99_OCTAL_CONSTANT 0[01234567]* INT_CONSTANT {C99_DECIMAL_CONSTANT}|{C99_HEXADECIMAL_CONSTANT}|{C99_OCTAL_CONSTANT} STRING_VALUE \"([^\"\\\n]|(\\[\"\\]))*\" SIMPLE_STRING [[:alnum:]]([[:alnum:]]|[[:space:]])* %% /* skip comments and whitespace */ ^[[:space:]]*#.*\n ; [[:space:]]|\n+ ; \[[[:space:]]*suite[[:space:]]+{IDENTIFIER}[[:space:]]*\] { free(suite.name.orig); suite.name.orig = parse_identifier(yytext); } caption{EQUALS} { charpp = &suite.caption; yy_push_state(SC_UNQUOTED_LINE); } title{EQUALS} { charpp = &suite.title; yy_push_state(SC_UNQUOTED_LINE); } mansect{EQUALS} { charpp = &suite.mansect; yy_push_state(SC_UNQUOTED_LINE); } date{EQUALS} { charpp = &suite.date; yy_push_state(SC_UNQUOTED_LINE); } version-string{EQUALS} { charpp = &suite.version_string; yy_push_state(SC_UNQUOTED_LINE); } manual_title{EQUALS} { charpp = &suite.manual_title; yy_push_state(SC_UNQUOTED_LINE); } aux_info_prefix{EQUALS} { charpp = &suite.aux_info_prefix; yy_push_state(SC_UNQUOTED_LINE); } aux_info_default{EQUALS} { charpp = &suite.aux_info_default; yy_push_state(SC_UNQUOTED_LINE); } \[[[:space:]]*introduction[[:space:]]*\] { text_buf = NULL; text_buf_len = 0; yy_push_state(SC_INTRODUCTION); } [[:space:]]*\[[[:space:]]*\/introduction[[:space:]]*\]\n { suite.introduction = text_buf; yy_pop_state(); } \[[[:space:]]*conclusion[[:space:]]*\] { text_buf = NULL; text_buf_len = 0; yy_push_state(SC_CONCLUSION); } [[:space:]]*\[[[:space:]]*\/conclusion[[:space:]]*\]\n { suite.conclusion = text_buf; yy_pop_state(); } \[[[:space:]]*supercommand[[:space:]]+{IDENTIFIER}[[:space:]]*\] { struct lsg_command *cmd; if (!suite.commands) suite.commands = xmalloc(sizeof(*suite.commands)); cmd = suite.commands; memset(cmd, 0, sizeof(*cmd)); cmd->name.orig = parse_identifier(yytext); cmd->options = xmalloc(sizeof(*cmd->options)); BEGIN(SC_COMMAND); } \[[[:space:]]*subcommand[[:space:]]+{IDENTIFIER}[[:space:]]*\] { int command_num = ++suite.num_subcommands; struct lsg_command *cmd; suite.commands = realloc(suite.commands, (suite.num_subcommands + 1) * sizeof(*suite.commands)); cmd = suite.commands + command_num; memset(cmd, 0, sizeof(*cmd)); cmd->name.orig = parse_identifier(yytext); cmd->options = xmalloc(sizeof(*cmd->options)); BEGIN(SC_COMMAND); } \[[[:space:]]*description[[:space:]]*\] { text_buf = NULL; text_buf_len = 0; yy_push_state(SC_DESCRIPTION); } \[[[:space:]]*closing[[:space:]]*\] { text_buf = NULL; text_buf_len = 0; yy_push_state(SC_CLOSING); } \[[[:space:]]*help[[:space:]]*\] { text_buf = NULL; text_buf_len = 0; yy_push_state(SC_HELP); } \[[[:space:]]*section[[:space:]]+{SIMPLE_STRING}[[:space:]]*\] { int sect_num = suite.num_sections++; suite.sections = realloc(suite.sections, suite.num_sections * sizeof(*suite.sections)); CURSECT.name.orig = parse_simple_string(); text_buf = NULL; text_buf_len = 0; yy_push_state(SC_SECTION); } [[:space:]]*\[[[:space:]]*\/description[[:space:]]*\]\n { CURCMD.description = text_buf; yy_pop_state(); } [[:space:]]*\[[[:space:]]*\/closing[[:space:]]*\]\n { CURCMD.closing = text_buf; yy_pop_state(); } [[:space:]]*\[[[:space:]]*\/help[[:space:]]*\]\n { CUROPT.help = text_buf; yy_pop_state(); } [[:space:]]*\[[[:space:]]*\/section[[:space:]]*\]\n { CURSECT.text = text_buf; yy_pop_state(); } .*\n { size_t new_len = text_buf_len + yyleng; size_t num_tabs = 0; while (yytext[num_tabs] == '\t') num_tabs++; new_len = text_buf_len + yyleng - num_tabs; text_buf = realloc(text_buf, new_len + 1); memcpy(text_buf + text_buf_len, yytext + num_tabs, yyleng - num_tabs); text_buf[new_len] = '\0'; text_buf_len = new_len; } \[[[:space:]]*option[[:space:]]+{IDENTIFIER}[[:space:]]*\] { int option_num = CURCMD.num_options++; struct lsg_option *opt; CURCMD.options = realloc(CURCMD.options, CURCMD.num_options * sizeof(*CURCMD.options)); memset(&CUROPT, 0, sizeof(CUROPT)); CUROPT.name.orig = parse_identifier(yytext); BEGIN(SC_OPTION); } arg_info{EQUALS} BEGIN(SC_ARG_INFO); no_arg CUROPT.arg_info = "LLS_NO_ARGUMENT"; BEGIN(SC_OPTION); required_arg CUROPT.arg_info = "LLS_REQUIRED_ARGUMENT"; BEGIN(SC_OPTION); optional_arg CUROPT.arg_info = "LLS_OPTIONAL_ARGUMENT"; BEGIN(SC_OPTION); arg_type{EQUALS} BEGIN(SC_ARG_TYPE); none CUROPT.arg_type = "LLS_NONE"; BEGIN(SC_OPTION); string CUROPT.arg_type = "LLS_STRING"; BEGIN(SC_OPTION); int32 CUROPT.arg_type = "LLS_INT32"; BEGIN(SC_OPTION); uint32 CUROPT.arg_type = "LLS_UINT32"; BEGIN(SC_OPTION); int64 CUROPT.arg_type = "LLS_INT64"; BEGIN(SC_OPTION); uint64 CUROPT.arg_type = "LLS_UINT64"; BEGIN(SC_OPTION); flag BEGIN(SC_OPTION_FLAG); multiple CUROPT.multiple = true; BEGIN(SC_OPTION); required CUROPT.required = true; BEGIN(SC_OPTION); ignored CUROPT.ignored = true; BEGIN(SC_OPTION); default_val{EQUALS} { charpp = &CUROPT.default_val; if (!CUROPT.arg_type || strcmp(CUROPT.arg_type, "LLS_NONE") == 0) { fprintf(stderr, "default_value for option w/o arguments!?\n"); exit(1); } else if (strcmp(CUROPT.arg_type, "LLS_STRING") == 0) yy_push_state(SC_UNQUOTED_LINE_CHECK_DEFAULT); else yy_push_state(SC_INTEGER); } short_opt{EQUALS} BEGIN(SC_CHAR); [a-zA-Z0-9] CUROPT.short_opt = yytext[0]; BEGIN(SC_OPTION); values{EQUALS}\{ { if (!CUROPT.arg_type || strcmp(CUROPT.arg_type, "LLS_STRING")) { fprintf(stderr, "value list is only supported for string options\n"); exit(EXIT_FAILURE); } if (!CUROPT.arg_info || !strcmp(CUROPT.arg_info, "LLS_NO_ARGUMENT")) { fprintf(stderr, "enum options must take an argument\n"); exit(EXIT_FAILURE); } if (CUROPT.default_val) { fprintf(stderr, "value list must preceed default value\n"); exit(EXIT_FAILURE); } BEGIN(SC_VALUES_ID); } {IDENTIFIER}{EQUALS}{STRING_VALUE} { parse_id_string(); BEGIN(SC_VALUES_COMMA_OR_BRACE); } [,\}] { if (*yytext == ',') BEGIN(SC_VALUES_ID); else { check_default_val(); BEGIN(SC_OPTION); } } summary{EQUALS} { charpp = &CUROPT.summary; yy_push_state(SC_UNQUOTED_LINE); } typestr{EQUALS} { charpp = &CUROPT.typestr; yy_push_state(SC_UNQUOTED_LINE); } purpose{EQUALS} { charpp = &CURCMD.purpose; yy_push_state(SC_UNQUOTED_LINE); } non-opts-name{EQUALS} { charpp = &CURCMD.non_opts_name; yy_push_state(SC_UNQUOTED_LINE); } synopsis{EQUALS} { charpp = &CURCMD.synopsis; yy_push_state(SC_UNQUOTED_LINE); } aux_info{EQUALS} { charpp = &CURCMD.aux_info; yy_push_state(SC_UNQUOTED_LINE); } .*\n { *charpp = parse_unquoted_line(); yy_pop_state(); } .*\n { *charpp = parse_unquoted_line(); check_default_val(); yy_pop_state(); } {INT_CONSTANT} *charpp = strdup(yytext); yy_pop_state(); /* This rule runs iff none of the above patterns matched */ . { fprintf(stderr, "parse error at line %d. Unmatched: \"%s\"\n", yyget_lineno(), yytext); BEGIN(SC_ERROR); } .*\n { fprintf(stderr, "subsequent unparsed input: %s\n", yytext); exit(EXIT_FAILURE); }