You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
431 lines
10 KiB
431 lines
10 KiB
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
|
|
#include "view.h"
|
|
|
|
#include "args.h"
|
|
|
|
#include "comp_features.h"
|
|
|
|
#define _PASTE_(x, y) x ## y
|
|
#define _PASTE(x, y) _PASTE_(x, y)
|
|
|
|
#define _box_name(nm) _PASTE(_box_, _PASTE(nm, __LINE__))
|
|
#define $box(expr) ({ __typeof((expr))* _box_name(p) = aligned_alloc(_Alignof(__typeof((expr))), sizeof(__typeof((expr)))); \
|
|
if(__builtin_expect(_box_name(p) != NULL, true)) *_box_name(p) = (expr); \
|
|
_box_name(p); })
|
|
|
|
#define $box_t(T, expr) ({ T* _box_name(pT) = aligned_alloc(_Alignof(T), sizeof(T)); \
|
|
if(__builtin_expect(_box_name(pT) != NULL, true)) *_box_name(pT) = (expr); \
|
|
_box_name(pT); })
|
|
|
|
typedef __typeof( *((pargs_t*)0)->fd_pass ) fd_pass_t;
|
|
typedef __typeof( ((pargs_t*)0)->replace ) replace_t;
|
|
|
|
struct input_arguments
|
|
{
|
|
int *restrict p_argc;
|
|
char** *restrict p_argv;
|
|
char** *restrict p_envp;
|
|
};
|
|
|
|
typedef bool (single_arg_parse_func)(int, const char*, struct input_arguments* restrict, pargs_t*);
|
|
typedef void (single_arg_error_func)(struct arg_parse_error* restrict);
|
|
|
|
enum argument_type {
|
|
AT_SHORT = 0,
|
|
AT_LONG,
|
|
AT_LONG_EQ,
|
|
|
|
AT_LONG_OR_EQ = AT_LONG | AT_LONG_EQ,
|
|
};
|
|
|
|
struct argument_parser {
|
|
union {
|
|
const char* l;
|
|
char s;
|
|
} name;
|
|
|
|
enum argument_type kind;
|
|
|
|
bool is_default;
|
|
|
|
const char* destription;
|
|
const char* example;
|
|
|
|
single_arg_parse_func* parse;
|
|
single_arg_error_func* error;
|
|
};
|
|
|
|
typedef __typeof( ((struct argument_parser*)0)->name ) av_name_t;
|
|
|
|
static bool _si_handle_replace(int idx, const char* arg, struct input_arguments* restrict in, pargs_t* out)
|
|
{
|
|
char c;
|
|
bool* restrict repl;
|
|
switch(c = *arg) {
|
|
case 'i':
|
|
case 'I':
|
|
repl = &out->replace.in;
|
|
break;
|
|
case 'o':
|
|
case 'O':
|
|
repl = &out->replace.out;
|
|
break;
|
|
case 'e':
|
|
case 'E':
|
|
repl = &out->replace.err;
|
|
break;
|
|
default: return false;
|
|
}
|
|
*repl = !(c & 0x20);
|
|
return true;
|
|
}
|
|
|
|
static const struct argument_parser AV_ARGS[] = {
|
|
{ { .s= 'i' }, AT_SHORT, true, "Sink stdin", NULL, _si_handle_replace, NULL },
|
|
{ { .s='o' }, AT_SHORT, true, "Sink stdout", NULL, _si_handle_replace, NULL },
|
|
{ { .s='e' }, AT_SHORT, false, "Sink stderr", NULL, _si_handle_replace, NULL },
|
|
|
|
{ { .s='I' }, AT_SHORT, false, "Keep stdin", NULL, _si_handle_replace, NULL },
|
|
{ { .s='O' }, AT_SHORT, false, "Keep stdout", NULL, _si_handle_replace, NULL },
|
|
{ { .s='E' }, AT_SHORT, true, "Keep stderr", NULL, _si_handle_replace, NULL },
|
|
};
|
|
#define AV_ARGS_SZ (sizeof(AV_ARGS) / sizeof(struct argument_parser))
|
|
|
|
static inline bool _av_long_extract_name(const char* name, enum argument_type ty, view_t* restrict out)
|
|
{
|
|
register bool ok = true;
|
|
switch(ty) {
|
|
case AT_LONG_EQ:
|
|
ok = !!sv_split_cstr(name, '=', out, NULL);
|
|
break;
|
|
case AT_LONG_OR_EQ:
|
|
if(!sv_split_cstr(name, '=', out, NULL)) {
|
|
if(0)
|
|
case AT_SHORT: name = (const char[2]){*name, 0};
|
|
*out = sv_from_cstr(name);
|
|
}
|
|
break;
|
|
default: return false;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
static inline const struct argument_parser* _av_lookup(av_name_t name, enum argument_type ty)
|
|
{
|
|
for(size_t i=0;i<AV_ARGS_SZ;i++) {
|
|
const struct argument_parser* current = AV_ARGS+i;
|
|
if(__builtin_constant_p(ty) && ty == AT_SHORT) {
|
|
if(current->name.s == name.s) return current;
|
|
} else switch(ty) {
|
|
case AT_SHORT: if(current->name.s == name.s) return current;
|
|
default: {
|
|
view_t real_name;
|
|
if(!_av_long_extract_name(name.l, ty, &real_name)) continue; // Argument requires `=` but one wasn't fount, continue.
|
|
if(sv_eq_cstr(real_name, current->name.l)) return current;
|
|
} break;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
const pargs_t* parsed_args = NULL;
|
|
static const pargs_t default_args[1] = { A_DEFAULT_ARGS }; //A_DEFAULT_ARGS_P;
|
|
|
|
__attribute__((__gnu_inline__, __always_inline__))
|
|
static inline size_t _a_ptr_list_len(void* const* restrict arr)
|
|
{
|
|
register size_t n=0;
|
|
while(*arr++) n+=1;
|
|
return n;
|
|
}
|
|
|
|
inline static void* _a_clone__string(const void* i) { return __builtin_expect(i!=NULL, true) ? strdup(i) : NULL; }
|
|
inline static void** _a_clone_ptr_list(void* const arr[static 1], void* (*clone_elem)(const void*), size_t* restrict _n)
|
|
{
|
|
size_t len;
|
|
if(__builtin_constant_p(_n) && _n) *_n = len = _a_ptr_list_len(arr);
|
|
else {
|
|
len = _a_ptr_list_len(arr);
|
|
if(_n) *_n = len;
|
|
}
|
|
|
|
void* *array = aligned_alloc(_Alignof(void*), sizeof(void*)*(len+1));
|
|
if(__builtin_expect(array!=NULL, true)) {
|
|
if(__builtin_constant_p(clone_elem) && !clone_elem)
|
|
memcpy(array, arr, sizeof(void*) * len);
|
|
else if(clone_elem) {
|
|
//void* *restrict out = array;
|
|
while(len --> 0) {
|
|
// *out++ = clone_elem(*arr++);
|
|
array[len] = clone_elem(arr[len]);
|
|
}
|
|
} else memcpy(array, arr, sizeof(void*) * len);
|
|
array[len] = NULL; // NULL-terminate ptr list
|
|
}
|
|
return array;
|
|
}
|
|
static char** _a_clone_string_list(char* const arr[static 1], size_t* restrict n)
|
|
{
|
|
return (char**)_a_clone_ptr_list((void* const*)arr, _a_clone__string, n);
|
|
}
|
|
|
|
inline static void _a_free_ptr_list(void* arr[static restrict 1], void (*free_elem)(void*), size_t* restrict _n)
|
|
{
|
|
if(__builtin_constant_p(_n) && __builtin_constant_p(free_elem) && !_n && !free_elem) {
|
|
free(arr);
|
|
return;
|
|
} else {
|
|
size_t n =0;
|
|
void* restrict _to_free = arr;
|
|
|
|
void* cur;
|
|
if(__builtin_constant_p(free_elem) && free_elem) {
|
|
if( __builtin_constant_p(_n) && !_n)
|
|
while( (cur = *arr++) ) free_elem(cur);
|
|
else
|
|
while( (cur = *arr++) )
|
|
{
|
|
free_elem(cur); n+=1;
|
|
}
|
|
} else if(!free_elem)
|
|
while( (cur = *arr++) ) n+=1;
|
|
else while( (cur = *arr++) )
|
|
{
|
|
free_elem(cur); n+=1;
|
|
}
|
|
|
|
free(_to_free);
|
|
|
|
if(_n) *_n = n;
|
|
}
|
|
}
|
|
static void _a_free_string_list(char* *restrict arr/*[static restrict 1]*/, size_t* restrict n)
|
|
{
|
|
_a_free_ptr_list((void**restrict)arr, free, n);
|
|
}
|
|
|
|
static fd_pass_t* _a_alloc_fd_pass(size_t n)
|
|
{
|
|
fd_pass_t* pass = aligned_alloc(_Alignof(fd_pass_t), sizeof(fd_pass_t) + (sizeof(int)*n));
|
|
if(__builtin_expect(pass!=NULL,true)) {
|
|
pass->n = n;
|
|
//memset(pass->fds, 0, sizeof(int) * n);
|
|
}
|
|
return pass;
|
|
}
|
|
|
|
void* a_pass_fds(size_t n, ...)
|
|
{
|
|
va_list va;
|
|
va_start(va, n);
|
|
fd_pass_t* pass;
|
|
if(!n) {
|
|
pass = A_FD_PASS_NONE;
|
|
goto _end;
|
|
}
|
|
pass = _a_alloc_fd_pass(n);
|
|
if(__builtin_expect(pass!=NULL, true)) {
|
|
int* fds = pass->fds;
|
|
while ( n --> 0 ) {
|
|
int fd = va_arg(va, int);
|
|
*fds++ = fd;
|
|
}
|
|
}
|
|
_end:
|
|
va_end(va);
|
|
return pass;
|
|
}
|
|
|
|
inline void* a_pass_fds_a(size_t n, int ar[const restrict n])
|
|
{
|
|
if(!n) return A_FD_PASS_NONE;
|
|
fd_pass_t* pass = _a_alloc_fd_pass(n);
|
|
if(__builtin_expect(pass!=NULL, true))
|
|
memcpy(pass->fds, ar, n * sizeof(int));
|
|
return pass;
|
|
}
|
|
|
|
const pargs_t* a_get_program_args() {
|
|
return parsed_args ?: default_args;
|
|
}
|
|
|
|
bool a_is_parsed() { return parsed_args != NULL; }
|
|
|
|
static bool a_parse__short(int i, const char* arg, struct input_arguments in, pargs_t* restrict out)
|
|
{
|
|
const struct argument_parser* p = _av_lookup( (av_name_t){ .s = *arg, }, AT_SHORT );
|
|
if(!p) return false;
|
|
else {
|
|
if(!p->parse(i, arg, &in, out)) {
|
|
if(p->error) {
|
|
//TODO: How to handle returning `struct arg_parse_error`? Make `out` union arg_parse_result* restrict` instead?
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool a_parse__long(int i, const char* arg, struct input_arguments in, pargs_t* restrict out)
|
|
{
|
|
//TODO: determine if `arg` is `k=v` or not
|
|
bool is_eq = /* TODO */ false;
|
|
const struct argument_parser* p = _av_lookup( (av_name_t){ .l = arg, }, is_eq ? AT_LONG_EQ : AT_LONG );
|
|
if(!p) return false;
|
|
else {
|
|
//TODO :if(!p->parse(
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
inline static bool _a_parse_into(union arg_parse_result *restrict parsed, struct input_arguments input)
|
|
{
|
|
bool ok = true;;
|
|
pargs_t args = A_DEFAULT_ARGS;
|
|
|
|
//TODO: Parse arguments
|
|
|
|
_end:
|
|
if(ok)
|
|
_set_ok: parsed->ok = args;
|
|
else
|
|
_set_err: parsed->err = (struct arg_parse_error) { .kind = A_PF_UNKNOWN, ._data = { .none = {} } };
|
|
|
|
return ok;
|
|
}
|
|
|
|
|
|
inline bool a_parse_into(union arg_parse_result *restrict parsed, int *restrict p_argc, char** *restrict p_argv, char** *restrict p_envp)
|
|
{
|
|
#if FEATURE_HAS_FLAG(NO_ARGS)
|
|
parsed->ok = A_DEFAULT_ARGS;
|
|
return true;
|
|
#else
|
|
struct input_arguments input = {
|
|
p_argc,
|
|
p_argv,
|
|
p_envp,
|
|
};
|
|
|
|
return _a_parse_into(parsed, input);
|
|
#endif
|
|
}
|
|
|
|
const struct arg_parse_error* a_parse(int *restrict argc, char** *restrict p_argv, char** *restrict p_envp)
|
|
{
|
|
/*XXX: Probably not needed... _Thread_local*/
|
|
static union arg_parse_result glob_parsed_args_r;
|
|
|
|
if(a_parse_into(&glob_parsed_args_r, argc, p_argv, p_envp))
|
|
parsed_args = &glob_parsed_args_r.ok; // A_P_OK
|
|
else
|
|
return &glob_parsed_args_r.err;
|
|
return NULL;
|
|
}
|
|
|
|
pargs_t* a_clone_args(const pargs_t* args)
|
|
{
|
|
if(__builtin_expect(args!=NULL, true)) {
|
|
pargs_t clone;
|
|
|
|
/* replace */ clone.replace = args->replace;
|
|
|
|
/* target */ {
|
|
const char* target_p = args->target;
|
|
clone.target = target_p ? strdup(target_p) : NULL;
|
|
};
|
|
|
|
/* fd_pass */ {
|
|
__auto_type fd_pass_p = args->fd_pass;
|
|
switch((uintptr_t)fd_pass_p) {
|
|
case ((uintptr_t)A_FD_PASS_ALL):
|
|
case ((uintptr_t)A_FD_PASS_NONE):
|
|
clone.fd_pass = fd_pass_p;
|
|
if(0)
|
|
default: {
|
|
// Clone fd_pass_p.
|
|
clone.fd_pass = a_pass_fds_a(fd_pass_p->n, fd_pass_p->fds);
|
|
} break;
|
|
}
|
|
};
|
|
|
|
/* env_pass */ {
|
|
char** env_pass_p = args->env_pass;
|
|
switch((uintptr_t)env_pass_p) {
|
|
case (uintptr_t)A_ENV_PASS_ALL:
|
|
case (uintptr_t)A_ENV_PASS_NONE:
|
|
clone.env_pass = env_pass_p;
|
|
if(0)
|
|
default: {
|
|
// Clone envp deeply.
|
|
clone.env_pass = _a_clone_string_list(env_pass_p, NULL);
|
|
|
|
} break;
|
|
}
|
|
};
|
|
|
|
return $box(clone);
|
|
} else return NULL;
|
|
}
|
|
|
|
inline void a_free_args(const pargs_t* args)
|
|
{
|
|
if(__builtin_expect(args==NULL, false)) return;
|
|
|
|
/* target */ {
|
|
char* target_p = args->target;
|
|
if(target_p) free(target_p);
|
|
};
|
|
|
|
/* fd_pass */ {
|
|
__auto_type fd_pass_p = args->fd_pass;
|
|
if(fd_pass_p != A_FD_PASS_NONE
|
|
&& fd_pass_p != A_FD_PASS_ALL) free(fd_pass_p);
|
|
};
|
|
|
|
/* env_pass */ {
|
|
char** env_pass_p = args->env_pass;
|
|
switch((uintptr_t)env_pass_p) {
|
|
case (uintptr_t)A_ENV_PASS_ALL:
|
|
case (uintptr_t)A_ENV_PASS_NONE:
|
|
if(0)
|
|
default: {
|
|
// Free each string in `env_pass_p`, then free `env_pass_p` itself
|
|
_a_free_string_list(env_pass_p, NULL);
|
|
} break;
|
|
}
|
|
};
|
|
}
|
|
|
|
void a_free(pargs_t* restrict args)
|
|
{
|
|
if(__builtin_expect(args!=NULL, true)) {
|
|
a_free_args(args);
|
|
free(args);
|
|
}
|
|
}
|
|
|
|
struct arg_parse_error* a_clone_error(const struct arg_parse_error* error)
|
|
{
|
|
/* no-op, for now */
|
|
if(__builtin_expect(error != NULL, true)) {
|
|
return $box_t(struct arg_parse_error, *error);
|
|
}
|
|
else return NULL;
|
|
}
|
|
|
|
void a_free_error(const struct arg_parse_error* error)
|
|
{
|
|
(void)error; // no-op, for now;
|
|
}
|
|
|
|
__attribute__((destructor))
|
|
static void _a__free_globals()
|
|
{
|
|
a_free_args(parsed_args);
|
|
}
|