libkernaux/src/cmdline.c

335 lines
9.0 KiB
C
Raw Normal View History

2020-12-07 04:46:37 +00:00
#ifdef HAVE_CONFIG_H
2020-12-01 19:55:16 +00:00
#include "config.h"
2020-12-07 04:46:37 +00:00
#endif
2020-12-01 19:55:16 +00:00
#include <kernaux/assert.h>
2020-12-01 19:55:16 +00:00
#include <kernaux/cmdline.h>
2022-06-27 14:46:29 +00:00
#include <kernaux/generic/file.h>
2022-06-27 23:13:40 +00:00
#include <stddef.h>
#include <string.h>
2020-12-06 10:16:15 +00:00
2020-12-04 02:41:45 +00:00
enum State {
INITIAL,
FINAL,
WHITESPACE,
TOKEN,
2021-12-12 15:47:22 +00:00
BACKSLASH,
QUOTE,
QUOTE_BACKSLASH,
2020-12-04 02:41:45 +00:00
};
2022-06-27 14:46:29 +00:00
static bool kernaux_cmdline_common(
const char *cmdline,
char *error_msg,
size_t *argc,
char arg_terminator,
char **argv,
char *buffer,
2022-06-27 16:30:47 +00:00
KernAux_File file,
size_t *arg_idxs,
size_t arg_count_max,
size_t buffer_size
2022-06-27 14:46:29 +00:00
);
2022-06-27 23:13:40 +00:00
static bool kernaux_cmdline_iter(
char cur,
enum State *state,
size_t *buffer_or_file_pos,
char *error_msg,
size_t *argc,
char arg_terminator,
char **argv,
char *buffer,
KernAux_File file,
size_t *arg_idxs,
size_t arg_count_max,
size_t buffer_size
);
/*******************************
* Implementations: public API *
*******************************/
2022-06-27 14:46:29 +00:00
bool kernaux_cmdline(
2020-12-01 20:22:38 +00:00
const char *const cmdline,
char *const error_msg,
2021-12-16 15:26:16 +00:00
size_t *const argc,
char **const argv,
char *const buffer,
2022-06-27 16:30:47 +00:00
const size_t arg_count_max,
2022-01-21 21:42:54 +00:00
const size_t buffer_size
2020-12-01 20:22:38 +00:00
) {
KERNAUX_ASSERT(cmdline);
KERNAUX_ASSERT(error_msg);
KERNAUX_ASSERT(argc);
KERNAUX_ASSERT(argv);
2022-06-27 16:30:47 +00:00
KERNAUX_ASSERT(arg_count_max > 0);
KERNAUX_ASSERT(buffer_size > 0);
2020-12-01 20:22:38 +00:00
2022-06-27 14:46:29 +00:00
return kernaux_cmdline_common(
cmdline,
error_msg,
argc,
'\0', // arg_terminator
argv,
buffer,
2022-06-27 16:30:47 +00:00
NULL,
NULL,
arg_count_max,
buffer_size
2022-06-27 14:46:29 +00:00
);
}
bool kernaux_cmdline_file(
const char *const cmdline,
char *const error_msg,
size_t *const argc,
2022-06-27 16:30:47 +00:00
const KernAux_File file,
size_t *arg_idxs,
size_t arg_count_max
2022-06-27 14:46:29 +00:00
) {
KERNAUX_ASSERT(cmdline);
KERNAUX_ASSERT(error_msg);
KERNAUX_ASSERT(argc);
KERNAUX_ASSERT(file);
2022-06-27 16:30:47 +00:00
KERNAUX_ASSERT(arg_idxs == NULL || arg_count_max > 0);
2022-06-27 14:46:29 +00:00
return kernaux_cmdline_common(
cmdline,
error_msg,
argc,
'\0', // arg_terminator
NULL,
NULL,
2022-06-27 16:30:47 +00:00
file,
arg_idxs,
arg_count_max,
0
2022-06-27 14:46:29 +00:00
);
}
2022-06-27 23:13:40 +00:00
/*********************************
* Implementation: main function *
*********************************/
2022-06-27 14:46:29 +00:00
2022-06-27 16:30:47 +00:00
#define CLEAR do { \
*argc = 0; \
if (argv) memset(argv, 0, sizeof(char*) * arg_count_max); \
if (buffer) memset(buffer, '\0', buffer_size); \
if (arg_idxs) memset(arg_idxs, 0, sizeof(size_t) * arg_count_max); \
} while (0)
2022-06-27 14:46:29 +00:00
bool kernaux_cmdline_common(
const char *const cmdline,
char *const error_msg,
size_t *const argc,
char arg_terminator,
char **const argv,
char *const buffer,
2022-06-27 16:30:47 +00:00
const KernAux_File file,
size_t *const arg_idxs,
const size_t arg_count_max,
const size_t buffer_size
2022-06-27 14:46:29 +00:00
) {
KERNAUX_ASSERT(cmdline);
KERNAUX_ASSERT(error_msg);
KERNAUX_ASSERT(argc);
2022-06-27 16:30:47 +00:00
(void)arg_idxs;
2022-06-27 14:46:29 +00:00
2021-12-15 10:45:40 +00:00
memset(error_msg, '\0', KERNAUX_CMDLINE_ERROR_MSG_SIZE_MAX);
2022-06-27 16:30:47 +00:00
CLEAR;
2020-12-01 20:22:38 +00:00
2022-01-21 21:42:54 +00:00
if (cmdline[0] == '\0') return true;
2020-12-04 02:41:45 +00:00
enum State state = INITIAL;
2022-06-27 16:30:47 +00:00
size_t buffer_or_file_pos = 0;
2022-06-27 23:13:40 +00:00
for (size_t index = 0; state != FINAL; ++index) {
const bool result = kernaux_cmdline_iter(
cmdline[index],
&state,
&buffer_or_file_pos,
error_msg,
argc,
arg_terminator,
argv,
buffer,
file,
arg_idxs,
arg_count_max,
buffer_size
);
if (!result) goto fail;
}
2020-12-06 10:16:15 +00:00
return true;
2020-12-01 21:54:18 +00:00
fail:
2022-06-27 16:30:47 +00:00
CLEAR;
2020-12-06 10:16:15 +00:00
return false;
2020-12-01 20:22:38 +00:00
}
2022-06-27 23:13:40 +00:00
/**************************************
* Implementation: iteration function *
**************************************/
#define FAIL(msg) do { \
strcpy(error_msg, msg); \
return false; \
} while (0)
#define PUT_CHAR(char) do { \
if (buffer_size && *buffer_or_file_pos >= buffer_size) { \
FAIL("EOF or buffer overflow"); \
} \
if (buffer) { \
buffer[*buffer_or_file_pos] = char; \
} \
if (file) { \
if (KernAux_File_putc(file, char) == KERNAUX_EOF) { \
FAIL("EOF or buffer overflow"); \
} \
} \
++(*buffer_or_file_pos); \
} while (0)
#define PUT_ARG do { \
if (arg_count_max && *argc >= arg_count_max) { \
FAIL("too many args"); \
} \
if (argv && buffer) { \
argv[*argc] = &buffer[*buffer_or_file_pos]; \
} \
if (arg_idxs) { \
arg_idxs[*argc] = *buffer_or_file_pos; \
} \
++(*argc); \
} while (0)
#define PUT_ARG_AND_CHAR(char) do { \
if (arg_count_max && *argc >= arg_count_max) { \
FAIL("too many args"); \
} \
if (buffer_size && *buffer_or_file_pos >= buffer_size) { \
FAIL("EOF or buffer overflow"); \
} \
if (argv && buffer) { \
argv[*argc] = &buffer[*buffer_or_file_pos]; \
buffer[*buffer_or_file_pos] = char; \
} \
if (file) { \
if (KernAux_File_putc(file, char) == KERNAUX_EOF) { \
FAIL("EOF or buffer overflow"); \
} \
} \
if (arg_idxs) { \
arg_idxs[*argc] = *buffer_or_file_pos; \
} \
++(*argc); \
++(*buffer_or_file_pos); \
} while (0)
bool kernaux_cmdline_iter(
const char cur,
enum State *const state,
size_t *const buffer_or_file_pos,
char *const error_msg,
size_t *const argc,
char arg_terminator,
char **const argv,
char *const buffer,
const KernAux_File file,
size_t *const arg_idxs,
const size_t arg_count_max,
const size_t buffer_size
) {
switch (*state) {
case FINAL:
break;
case INITIAL:
if (cur == '\0') {
*state = FINAL;
} else if (cur == ' ') {
*state = WHITESPACE;
} else if (cur == '\\') {
*state = BACKSLASH;
PUT_ARG;
} else if (cur == '"') {
*state = QUOTE;
PUT_ARG;
} else {
*state = TOKEN;
PUT_ARG_AND_CHAR(cur);
}
break;
case WHITESPACE:
if (cur == '\0') {
*state = FINAL;
} else if (cur == ' ') {
// do nothing
} else if (cur == '\\') {
*state = BACKSLASH;
PUT_ARG;
} else if (cur == '"') {
*state = QUOTE;
PUT_ARG;
} else {
*state = TOKEN;
PUT_ARG_AND_CHAR(cur);
}
break;
case TOKEN:
if (cur == '\0') {
*state = FINAL;
PUT_CHAR(arg_terminator);
} else if (cur == ' ') {
*state = WHITESPACE;
PUT_CHAR(arg_terminator);
} else if (cur == '\\') {
*state = BACKSLASH;
} else if (cur == '"') {
FAIL("unescaped quotation mark");
} else {
PUT_CHAR(cur);
}
break;
case BACKSLASH:
if (cur == '\0') {
FAIL("EOL after backslash");
} else {
*state = TOKEN;
PUT_CHAR(cur);
}
break;
case QUOTE:
if (cur == '\0') {
FAIL("EOL inside quote");
} else if (cur == '\\') {
*state = QUOTE_BACKSLASH;
} else if (cur == '"') {
*state = WHITESPACE;
PUT_CHAR(arg_terminator);
} else {
PUT_CHAR(cur);
}
break;
case QUOTE_BACKSLASH:
if (cur == '\0') {
FAIL("EOL after backslash inside quote");
} else {
*state = QUOTE;
PUT_CHAR(cur);
}
break;
}
return true;
}