libkernaux/src/cmdline.c

335 lines
9.0 KiB
C

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <kernaux/assert.h>
#include <kernaux/cmdline.h>
#include <kernaux/generic/file.h>
#include <stddef.h>
#include <string.h>
enum State {
INITIAL,
FINAL,
WHITESPACE,
TOKEN,
BACKSLASH,
QUOTE,
QUOTE_BACKSLASH,
};
static bool kernaux_cmdline_common(
const char *cmdline,
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
);
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 *
*******************************/
bool kernaux_cmdline(
const char *const cmdline,
char *const error_msg,
size_t *const argc,
char **const argv,
char *const buffer,
const size_t arg_count_max,
const size_t buffer_size
) {
KERNAUX_ASSERT(cmdline);
KERNAUX_ASSERT(error_msg);
KERNAUX_ASSERT(argc);
KERNAUX_ASSERT(argv);
KERNAUX_ASSERT(arg_count_max > 0);
KERNAUX_ASSERT(buffer_size > 0);
return kernaux_cmdline_common(
cmdline,
error_msg,
argc,
'\0', // arg_terminator
argv,
buffer,
NULL,
NULL,
arg_count_max,
buffer_size
);
}
bool kernaux_cmdline_file(
const char *const cmdline,
char *const error_msg,
size_t *const argc,
const KernAux_File file,
size_t *arg_idxs,
size_t arg_count_max
) {
KERNAUX_ASSERT(cmdline);
KERNAUX_ASSERT(error_msg);
KERNAUX_ASSERT(argc);
KERNAUX_ASSERT(file);
KERNAUX_ASSERT(arg_idxs == NULL || arg_count_max > 0);
return kernaux_cmdline_common(
cmdline,
error_msg,
argc,
'\0', // arg_terminator
NULL,
NULL,
file,
arg_idxs,
arg_count_max,
0
);
}
/*********************************
* Implementation: main function *
*********************************/
#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)
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,
const KernAux_File file,
size_t *const arg_idxs,
const size_t arg_count_max,
const size_t buffer_size
) {
KERNAUX_ASSERT(cmdline);
KERNAUX_ASSERT(error_msg);
KERNAUX_ASSERT(argc);
(void)arg_idxs;
memset(error_msg, '\0', KERNAUX_CMDLINE_ERROR_MSG_SIZE_MAX);
CLEAR;
if (cmdline[0] == '\0') return true;
enum State state = INITIAL;
size_t buffer_or_file_pos = 0;
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;
}
return true;
fail:
CLEAR;
return false;
}
/**************************************
* 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;
}