/* * Copyright (C) 2016 Andre Noll * * Licensed under the LGPL v3, see http://www.gnu.org/licenses/lgpl-3.0.html */ /* We don't want symbols to clash with those of other flex users. */ %option prefix="lls_yy" /* Generate a scanner which is safe to use in multithreaded programs. */ %option reentrant /* To maintain state in a reentrant way. */ %option extra-type="struct lls_yy_config_file_extra *" %option stack %option never-interactive %option yylineno %x SC_ARG %s SC_SCANNING IDENTIFIER [a-zA-Z]+[a-zA-Z0-9_-]* EQUALS [[:space:]]*=[[:space:]]* OPTION [a-zA-Z]+[a-zA-Z0-9_-]* %{ #include #include #include #include #include #include "lopsub-internal.h" #include "lopsub.h" struct lls_yy_config_file_extra { int argc; char **argv; const char *subcmd; }; static int add_option(yyscan_t yyscanner); static int parse_arg(char **result, yyscan_t yyscanner); static int yywrap(yyscan_t yyscanner) {return 1;} %} %% /* skip comments and whitespace */ ^[[:space:]]*#.*\n ; [[:space:]]|\n+ ; \[[[:space:]]*{IDENTIFIER}[[:space:]]*\][[:space:]]*\n { int i, j; const char *subcmd = yyget_extra(yyscanner)->subcmd; assert(yytext[0] == '['); if (!subcmd) return 0; for (i = 1; i < yyleng; i++) if (!isspace(yytext[i])) break; for (j = i; j < yyleng; j++) if (yytext[j] == ']' || isspace(yytext[j])) break; assert(j < yyleng); yytext[j] = '\0'; if (strcmp(yytext + i, subcmd)) BEGIN(INITIAL); else BEGIN(SC_SCANNING); } {OPTION}[[:space:]]*\n add_option(yyscanner); {OPTION}({EQUALS}|[[:space:]]+) { int ret = add_option(yyscanner); if (ret < 0) return ret; BEGIN(SC_ARG); } .*\n { struct lls_yy_config_file_extra *extra = yyget_extra(yyscanner); const char *opt = extra->argv[extra->argc - 1]; char *arg, *result; size_t opt_len = strlen(opt), arg_len; int ret = parse_arg(&arg, yyscanner); if (ret < 0) return ret; arg_len = ret; result = malloc(opt_len + arg_len + 2); if (!result) return -E_LLS_NOMEM; strcpy(result, opt); result[opt_len] = '='; strcpy(result + opt_len + 1, arg); free(arg); result[opt_len + arg_len + 1] = '\0'; free(extra->argv[extra->argc - 1]); extra->argv[extra->argc - 1] = result; BEGIN(SC_SCANNING); } .*\n {} /* This rule runs iff none of the above patterns matched */ . {return -1;} %% #include #include #include #include static int expand_result(yyscan_t yyscanner) { struct lls_yy_config_file_extra *extra = yyget_extra(yyscanner); int nargc = extra->argc + 1; char **nrargv = realloc(extra->argv, (nargc + 1) * sizeof(char *)); if (!nrargv) return -E_LLS_NOMEM; extra->argc = nargc; extra->argv = nrargv; extra->argv[extra->argc] = NULL; return 1; } static int add_option(yyscan_t yyscanner) { struct lls_yy_config_file_extra *extra = yyget_extra(yyscanner); int ret; unsigned n; int len = yyget_leng(yyscanner); char *narg, *text = yyget_text(yyscanner); for (n = 0; n < len; n++) if (!isalnum(text[n]) && !(text[n] == '-')) break; assert(n > 0); ret = expand_result(yyscanner); if (ret < 0) return ret; narg = malloc(n + 2 + 1); if (!narg) return -E_LLS_NOMEM; narg[0] = narg[1] = '-'; memcpy(narg + 2, text, n); narg[n + 2] = '\0'; extra->argv[extra->argc - 1] = narg; return 1; } static int parse_arg(char **result, yyscan_t yyscanner) { bool backslash = false, quote = false; const char *in; char *out; int ret; int len = yyget_leng(yyscanner); char *text = yyget_text(yyscanner); *result = malloc(len + 1); if (!*result) return -E_LLS_NOMEM; for (in = text, out = *result; *in; in++) { if (*in == '\\') { if (!backslash) { backslash = true; continue; } } else if (*in == 'n' || *in == 't') { if (backslash) { /* \n or \t */ *out++ = (*in == 'n')? '\n' : '\t'; backslash = false; continue; } } else if (*in == '"') { if (!backslash) { quote = !quote; continue; } } else if (isspace(*in)) { if (!backslash && !quote) break; } /* copy the character */ *out++ = *in; backslash = false; } ret = -E_LLS_TRAILING_BACKSLASH; if (backslash) goto fail; ret = -E_LLS_UNMATCHED_QUOTE; if (quote) goto fail; /* look at first non-space character */ for (; *in; in++) { if (isspace(*in)) continue; if (*in == '#') break; ret = -E_LLS_TRAILING_GARBAGE; goto fail; } /* success */ *out = '\0'; return out - *result; fail: assert(ret < 0); free(*result); *result = NULL; return ret; } void lls_free_argv(char **argv) { int i; if (!argv) return; for (i = 0; argv[i]; i++) free(argv[i]); free(argv); } int lls_convert_config(const char *buf, size_t nbytes, const char *subcmd, char ***result, char **errctx) { int ret; YY_BUFFER_STATE yybs; yyscan_t yyscanner; struct lls_yy_config_file_extra extra; *result = NULL; if (errctx) *errctx = NULL; extra.argc = 1; extra.argv = malloc((extra.argc + 1) * sizeof(char *)); if (!extra.argv) return -E_LLS_NOMEM; extra.argv[0] = strdup(__FUNCTION__); if (!extra.argv[0]) { free(extra.argv); return -E_LLS_NOMEM; } extra.argv[1] = NULL; extra.subcmd = subcmd; ret = yylex_init_extra(&extra, &yyscanner); if (ret != 0) { ret = -E_LLS_NOMEM; goto free_argv; } yy_push_state(subcmd? INITIAL : SC_SCANNING, yyscanner); yybs = yy_scan_bytes(buf, nbytes, yyscanner); if (!yybs) { ret = -E_LLS_YY_SCAN; goto destroy; } ret = yylex(yyscanner); if (ret < 0) { ret = -E_LLS_YY_LEX; if (errctx) { *errctx = malloc(100); if (*errctx) sprintf(*errctx, "error at line %d", yyget_lineno(yyscanner)); } } yy_delete_buffer(yybs, yyscanner); destroy: yylex_destroy(yyscanner); free_argv: if (ret < 0) lls_free_argv(extra.argv); else { *result = extra.argv; ret = extra.argc; } return ret; } #if 0 int main(void) { char buf[100 * 1024]; int ret, len, i, argc; char **argv; ret = read(STDIN_FILENO, buf, sizeof(buf)); if (ret <= 0) exit(EXIT_FAILURE); len = ret; ret = lls_convert_config(buf, len, NULL, &argv, NULL); if (ret < 0) exit(EXIT_FAILURE); argc = ret; for (i = 0; i < argc; i++) printf("argv[%d]: %s\n", i, rargv[i]); return EXIT_SUCCESS; } #endif