mirror of
https://git.openwrt.org/openwrt/openwrt.git
synced 2026-01-28 03:37:17 +01:00
Introduce a check for the resource data in order to avoid segfaulting on exit Signed-off-by: Felix Fietkau <nbd@nbd.name>
908 lines
19 KiB
C
908 lines
19 KiB
C
// SPDX-License-Identifier: ISC
|
|
/*
|
|
* Copyright (C) 2025 Felix Fietkau <nbd@nbd.name>
|
|
*/
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <poll.h>
|
|
|
|
#include <ucode/module.h>
|
|
#include <libubox/list.h>
|
|
#include <libubox/uloop.h>
|
|
|
|
#include "uline.h"
|
|
|
|
static uc_value_t *registry;
|
|
static uc_resource_type_t *state_type, *argp_type;
|
|
|
|
enum {
|
|
STATE_RES,
|
|
STATE_CB,
|
|
STATE_INPUT,
|
|
STATE_OUTPUT,
|
|
STATE_POLL_CB,
|
|
};
|
|
|
|
struct uc_uline_state {
|
|
struct uloop_fd fd;
|
|
|
|
struct uline_state s;
|
|
int registry_index;
|
|
|
|
uc_vm_t *vm;
|
|
uc_value_t *state, *cb, *res, *poll_cb;
|
|
|
|
uc_value_t *line;
|
|
|
|
uint32_t input_mask[256 / 32];
|
|
};
|
|
|
|
struct uc_arg_parser {
|
|
char line_sep;
|
|
};
|
|
|
|
static unsigned int
|
|
registry_set(uc_vm_t *vm, uc_value_t *val)
|
|
{
|
|
uc_value_t *registry;
|
|
size_t i, len;
|
|
|
|
registry = uc_vm_registry_get(vm, "uline.registry");
|
|
len = ucv_array_length(registry);
|
|
for (i = 0; i < len; i++)
|
|
if (ucv_array_get(registry, i) == NULL)
|
|
break;
|
|
|
|
ucv_array_set(registry, i, ucv_get(val));
|
|
return i;
|
|
}
|
|
|
|
static uc_value_t *
|
|
uc_uline_poll(uc_vm_t *vm, size_t nargs)
|
|
{
|
|
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
|
uc_value_t *val;
|
|
|
|
if (!us)
|
|
return NULL;
|
|
|
|
uline_poll(&us->s);
|
|
val = us->line;
|
|
us->line = NULL;
|
|
|
|
return val;
|
|
}
|
|
|
|
static uc_value_t *
|
|
uc_uline_poll_key(uc_vm_t *vm, size_t nargs)
|
|
{
|
|
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
|
uc_value_t *timeout_arg = uc_fn_arg(0);
|
|
struct pollfd pfd = {};
|
|
int timeout, len;
|
|
char c;
|
|
|
|
if (!us)
|
|
return NULL;
|
|
|
|
if (ucv_type(timeout_arg) == UC_INTEGER)
|
|
timeout = ucv_int64_get(timeout_arg);
|
|
else
|
|
timeout = -1;
|
|
|
|
pfd.fd = us->s.input;
|
|
pfd.events = POLLIN;
|
|
poll(&pfd, 1, timeout);
|
|
if (!(pfd.revents & POLLIN))
|
|
return NULL;
|
|
|
|
do {
|
|
len = read(pfd.fd, &c, 1);
|
|
} while (len < 0 && errno == EINTR);
|
|
|
|
if (len != 1)
|
|
return NULL;
|
|
|
|
return ucv_string_new_length(&c, 1);
|
|
}
|
|
|
|
static uc_value_t *
|
|
uc_uline_poll_stop(uc_vm_t *vm, size_t nargs)
|
|
{
|
|
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
|
|
|
if (!us)
|
|
return NULL;
|
|
|
|
us->s.stop = true;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static uc_value_t *
|
|
uc_uline_get_window(uc_vm_t *vm, size_t nargs)
|
|
{
|
|
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
|
uc_value_t *val;
|
|
|
|
if (!us)
|
|
return NULL;
|
|
|
|
val = ucv_object_new(vm);
|
|
ucv_object_add(val, "x", ucv_int64_new(us->s.cols));
|
|
ucv_object_add(val, "y", ucv_int64_new(us->s.rows));
|
|
return val;
|
|
}
|
|
|
|
static uc_value_t *
|
|
uc_uline_get_line(uc_vm_t *vm, size_t nargs)
|
|
{
|
|
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
|
uc_value_t *line2 = uc_fn_arg(0);
|
|
uc_value_t *state;
|
|
const char *line;
|
|
size_t len;
|
|
|
|
if (!us)
|
|
return NULL;
|
|
|
|
state = ucv_object_new(vm);
|
|
if (ucv_is_truish(line2))
|
|
uline_get_line2(&us->s, &line, &len);
|
|
else
|
|
uline_get_line(&us->s, &line, &len);
|
|
ucv_object_add(state, "line", ucv_string_new_length(line, len));
|
|
ucv_object_add(state, "pos", ucv_int64_new(us->s.line.pos));
|
|
|
|
return state;
|
|
}
|
|
|
|
static uc_value_t *
|
|
uc_uline_set_state(uc_vm_t *vm, size_t nargs)
|
|
{
|
|
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
|
uc_value_t *state = uc_fn_arg(0);
|
|
uc_value_t *arg;
|
|
bool found;
|
|
|
|
if (!us || ucv_type(state) != UC_OBJECT)
|
|
return NULL;
|
|
|
|
if ((arg = ucv_object_get(state, "prompt", NULL)) != NULL) {
|
|
if (ucv_type(arg) != UC_STRING)
|
|
return NULL;
|
|
|
|
uline_set_prompt(&us->s, ucv_string_get(arg));
|
|
}
|
|
|
|
if ((arg = ucv_object_get(state, "line", NULL)) != NULL) {
|
|
if (ucv_type(arg) != UC_STRING)
|
|
return NULL;
|
|
|
|
uline_set_line(&us->s, ucv_string_get(arg), ucv_string_length(arg));
|
|
}
|
|
|
|
if ((arg = ucv_object_get(state, "pos", NULL)) != NULL) {
|
|
if (ucv_type(arg) != UC_INTEGER)
|
|
return NULL;
|
|
|
|
uline_set_cursor(&us->s, ucv_int64_get(arg));
|
|
}
|
|
|
|
arg = ucv_object_get(state, "line2_prompt", &found);
|
|
if (found) {
|
|
if (!arg)
|
|
uline_set_line2_prompt(&us->s, NULL);
|
|
else if (ucv_type(arg) == UC_STRING)
|
|
uline_set_line2_prompt(&us->s, ucv_string_get(arg));
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
if ((arg = ucv_object_get(state, "line2", NULL)) != NULL) {
|
|
if (ucv_type(arg) != UC_STRING)
|
|
return NULL;
|
|
|
|
uline_set_line2(&us->s, ucv_string_get(arg), ucv_string_length(arg));
|
|
}
|
|
|
|
if ((arg = ucv_object_get(state, "line2_pos", NULL)) != NULL) {
|
|
if (ucv_type(arg) != UC_INTEGER)
|
|
return NULL;
|
|
|
|
uline_set_line2_cursor(&us->s, ucv_int64_get(arg));
|
|
}
|
|
|
|
return ucv_boolean_new(true);
|
|
}
|
|
|
|
static uc_value_t *
|
|
uc_uline_set_hint(uc_vm_t *vm, size_t nargs)
|
|
{
|
|
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
|
uc_value_t *arg = uc_fn_arg(0);
|
|
|
|
if (!us || ucv_type(arg) != UC_STRING)
|
|
return NULL;
|
|
|
|
uline_set_hint(&us->s, ucv_string_get(arg), ucv_string_length(arg));
|
|
|
|
return ucv_boolean_new(true);
|
|
}
|
|
|
|
static uc_value_t *
|
|
uc_uline_set_uloop(uc_vm_t *vm, size_t nargs)
|
|
{
|
|
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
|
uc_value_t *cb = uc_fn_arg(0);
|
|
|
|
if (!us || (cb && !ucv_is_callable(cb)))
|
|
return NULL;
|
|
|
|
us->poll_cb = cb;
|
|
ucv_array_set(us->state, STATE_POLL_CB, ucv_get(cb));
|
|
if (cb) {
|
|
uloop_fd_add(&us->fd, ULOOP_READ);
|
|
us->fd.cb(&us->fd, 0);
|
|
} else {
|
|
uloop_fd_delete(&us->fd);
|
|
}
|
|
|
|
return ucv_boolean_new(true);
|
|
}
|
|
|
|
static uc_value_t *
|
|
uc_uline_reset_key_input(uc_vm_t *vm, size_t nargs)
|
|
{
|
|
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
|
|
|
us->s.repeat_char = 0;
|
|
|
|
return ucv_boolean_new(true);
|
|
}
|
|
|
|
static uc_value_t *
|
|
uc_uline_hide_prompt(uc_vm_t *vm, size_t nargs)
|
|
{
|
|
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
|
|
|
if (!us)
|
|
return NULL;
|
|
|
|
uline_hide_prompt(&us->s);
|
|
|
|
return ucv_boolean_new(true);
|
|
}
|
|
|
|
static uc_value_t *
|
|
uc_uline_refresh_prompt(uc_vm_t *vm, size_t nargs)
|
|
{
|
|
struct uc_uline_state *us = uc_fn_thisval("uline.state");
|
|
|
|
if (!us)
|
|
return NULL;
|
|
|
|
uline_refresh_prompt(&us->s);
|
|
|
|
return ucv_boolean_new(true);
|
|
}
|
|
|
|
static bool
|
|
cb_prepare(struct uc_uline_state *us, const char *name)
|
|
{
|
|
uc_value_t *func;
|
|
|
|
func = ucv_object_get(us->cb, name, NULL);
|
|
if (!func)
|
|
return false;
|
|
|
|
uc_vm_stack_push(us->vm, ucv_get(us->res));
|
|
uc_vm_stack_push(us->vm, ucv_get(func));
|
|
return true;
|
|
}
|
|
|
|
static uc_value_t *
|
|
cb_call_ret(struct uc_uline_state *us, size_t args, ...)
|
|
{
|
|
uc_vm_t *vm = us->vm;
|
|
va_list ap;
|
|
|
|
va_start(ap, args);
|
|
for (size_t i = 0; i < args; i++)
|
|
uc_vm_stack_push(vm, ucv_get(va_arg(ap, void *)));
|
|
va_end(ap);
|
|
|
|
if (uc_vm_call(vm, true, args) == EXCEPTION_NONE)
|
|
return uc_vm_stack_pop(vm);
|
|
|
|
return NULL;
|
|
}
|
|
#define cb_call(...) ucv_put(cb_call_ret(__VA_ARGS__))
|
|
|
|
static bool
|
|
uc_uline_cb_line(struct uline_state *s, const char *str, size_t len)
|
|
{
|
|
struct uc_uline_state *us = container_of(s, struct uc_uline_state, s);
|
|
bool complete = true;
|
|
uc_value_t *ret;
|
|
|
|
if (cb_prepare(us, "line_check")) {
|
|
ret = cb_call_ret(us, 1, ucv_string_new_length(str, len));
|
|
complete = ucv_is_truish(ret);
|
|
ucv_put(ret);
|
|
}
|
|
|
|
s->stop = complete;
|
|
if (complete)
|
|
us->line = ucv_string_new_length(str, len);
|
|
|
|
return complete;
|
|
}
|
|
|
|
static void
|
|
uc_uline_cb_event(struct uline_state *s, enum uline_event ev)
|
|
{
|
|
struct uc_uline_state *us = container_of(s, struct uc_uline_state, s);
|
|
static const char * const ev_types[] = {
|
|
[EDITLINE_EV_CURSOR_UP] = "cursor_up",
|
|
[EDITLINE_EV_CURSOR_DOWN] = "cursor_down",
|
|
[EDITLINE_EV_WINDOW_CHANGED] = "window_changed",
|
|
[EDITLINE_EV_EOF] = "eof",
|
|
[EDITLINE_EV_INTERRUPT] = "interrupt",
|
|
};
|
|
|
|
if (ev > ARRAY_SIZE(ev_types) || !ev_types[ev])
|
|
return;
|
|
|
|
if (!cb_prepare(us, ev_types[ev]))
|
|
return;
|
|
|
|
if (ev == EDITLINE_EV_WINDOW_CHANGED)
|
|
cb_call(us, 2, ucv_int64_new(s->cols), ucv_int64_new(s->rows));
|
|
else
|
|
cb_call(us, 0);
|
|
}
|
|
|
|
static void uc_uline_poll_cb(struct uloop_fd *fd, unsigned int events)
|
|
{
|
|
struct uc_uline_state *us = container_of(fd, struct uc_uline_state, fd);
|
|
uc_value_t *res = ucv_get(us->res);
|
|
uc_value_t *val;
|
|
|
|
while (!uloop_cancelled && ucv_resource_data(res, NULL) && us->poll_cb) {
|
|
uline_poll(&us->s);
|
|
|
|
val = us->line;
|
|
if (!val)
|
|
break;
|
|
|
|
us->line = NULL;
|
|
if (!ucv_is_callable(us->poll_cb))
|
|
break;
|
|
|
|
uc_vm_stack_push(us->vm, ucv_get(res));
|
|
uc_vm_stack_push(us->vm, ucv_get(us->poll_cb));
|
|
cb_call(us, 1, val);
|
|
}
|
|
ucv_put(res);
|
|
}
|
|
|
|
static bool
|
|
uc_uline_cb_key_input(struct uline_state *s, unsigned char c, unsigned int count)
|
|
{
|
|
struct uc_uline_state *us = container_of(s, struct uc_uline_state, s);
|
|
uc_value_t *ret;
|
|
bool retval;
|
|
|
|
if (!(us->input_mask[c / 32] & (1 << (c % 32))))
|
|
return false;
|
|
|
|
if (!cb_prepare(us, "key_input"))
|
|
return false;
|
|
|
|
ret = cb_call_ret(us, 2, ucv_string_new_length((char *)&c, 1), ucv_int64_new(count));
|
|
retval = ucv_is_truish(ret);
|
|
ucv_put(ret);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static void
|
|
uc_uline_cb_line2_update(struct uline_state *s, const char *str, size_t len)
|
|
{
|
|
struct uc_uline_state *us = container_of(s, struct uc_uline_state, s);
|
|
|
|
if (cb_prepare(us, "line2_update"))
|
|
cb_call(us, 1, ucv_string_new_length(str, len));
|
|
}
|
|
|
|
static bool
|
|
uc_uline_cb_line2_cursor(struct uline_state *s)
|
|
{
|
|
struct uc_uline_state *us = container_of(s, struct uc_uline_state, s);
|
|
uc_value_t *retval;
|
|
bool ret = true;
|
|
|
|
if (cb_prepare(us, "line2_cursor")) {
|
|
retval = cb_call_ret(us, 0);
|
|
ret = ucv_is_truish(retval);
|
|
ucv_put(retval);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool
|
|
uc_uline_cb_line2_newline(struct uline_state *s, const char *str, size_t len)
|
|
{
|
|
struct uc_uline_state *us = container_of(s, struct uc_uline_state, s);
|
|
uc_value_t *retval;
|
|
bool ret = false;
|
|
|
|
if (cb_prepare(us, "line2_newline")) {
|
|
retval = cb_call_ret(us, 1, ucv_string_new_length(str, len));
|
|
ret = ucv_is_truish(retval);
|
|
ucv_put(retval);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static uc_value_t *
|
|
uc_uline_new(uc_vm_t *vm, size_t nargs)
|
|
{
|
|
static const struct uline_cb uline_cb = {
|
|
#define _CB(_type) ._type = uc_uline_cb_##_type
|
|
_CB(key_input),
|
|
_CB(line),
|
|
_CB(event),
|
|
_CB(line2_update),
|
|
_CB(line2_cursor),
|
|
_CB(line2_newline),
|
|
#undef _CB
|
|
};
|
|
uc_value_t *data = uc_fn_arg(0);
|
|
struct uc_uline_state *us;
|
|
FILE *input, *output;
|
|
uc_value_t *arg, *cb, *state, *res;
|
|
|
|
if (ucv_type(data) != UC_OBJECT)
|
|
return NULL;
|
|
|
|
cb = ucv_object_get(data, "cb", NULL);
|
|
if (ucv_type(cb) != UC_OBJECT)
|
|
return NULL;
|
|
|
|
state = ucv_array_new(vm);
|
|
ucv_array_set(state, 0, ucv_get(cb));
|
|
if ((arg = ucv_object_get(data, "input", NULL)) != NULL) {
|
|
input = ucv_resource_data(arg, "fs.file");
|
|
ucv_array_set(state, STATE_INPUT, ucv_get(arg));
|
|
} else {
|
|
input = stdin;
|
|
}
|
|
|
|
if ((arg = ucv_object_get(data, "output", NULL)) != NULL) {
|
|
output = ucv_resource_data(arg, "fs.file");
|
|
ucv_array_set(state, STATE_OUTPUT, ucv_get(arg));
|
|
} else {
|
|
output = stdout;
|
|
}
|
|
|
|
if (!input || !output) {
|
|
input = output = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
us = calloc(1, sizeof(*us));
|
|
us->vm = vm;
|
|
us->state = ucv_array_new(vm);
|
|
ucv_array_set(us->state, STATE_CB, ucv_get(cb));
|
|
us->cb = cb;
|
|
us->registry_index = registry_set(vm, state);
|
|
|
|
if ((arg = ucv_object_get(data, "key_input_list", NULL)) != NULL) {
|
|
uc_value_t *val;
|
|
size_t len;
|
|
|
|
if (ucv_type(arg) != UC_ARRAY)
|
|
goto free;
|
|
|
|
len = ucv_array_length(arg);
|
|
for (size_t i = 0; i < len; i++) {
|
|
unsigned char c;
|
|
|
|
val = ucv_array_get(arg, i);
|
|
if (ucv_type(val) != UC_STRING || ucv_string_length(val) != 1)
|
|
goto free;
|
|
|
|
c = ucv_string_get(val)[0];
|
|
us->input_mask[c / 32] |= 1 << (c % 32);
|
|
}
|
|
}
|
|
|
|
res = ucv_resource_new(state_type, us);
|
|
ucv_array_set(us->state, STATE_RES, ucv_get(res));
|
|
us->res = res;
|
|
us->fd.fd = fileno(input);
|
|
us->fd.cb = uc_uline_poll_cb;
|
|
|
|
uline_init(&us->s, &uline_cb, us->fd.fd, output, true);
|
|
|
|
return res;
|
|
|
|
free:
|
|
free(us);
|
|
return NULL;
|
|
}
|
|
|
|
static void free_state(void *ptr)
|
|
{
|
|
struct uc_uline_state *us = ptr;
|
|
uc_value_t *registry;
|
|
|
|
if (!us)
|
|
return;
|
|
|
|
uloop_fd_delete(&us->fd);
|
|
registry = uc_vm_registry_get(us->vm, "uline.registry");
|
|
ucv_array_set(registry, us->registry_index, NULL);
|
|
uline_free(&us->s);
|
|
free(us);
|
|
}
|
|
|
|
static uc_value_t *
|
|
uc_uline_close(uc_vm_t *vm, size_t nargs)
|
|
{
|
|
struct uline_state **s = uc_fn_this("uline.state");
|
|
|
|
if (!s || !*s)
|
|
return NULL;
|
|
|
|
free_state(*s);
|
|
*s = NULL;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static bool
|
|
skip_space(const char **str, const char *end)
|
|
{
|
|
while (*str < end && isspace(**str))
|
|
(*str)++;
|
|
return *str < end;
|
|
}
|
|
|
|
static void
|
|
add_str(uc_stringbuf_t **buf, const char *str, const char *next)
|
|
{
|
|
if (str == next)
|
|
return;
|
|
|
|
if (!*buf)
|
|
*buf = ucv_stringbuf_new();
|
|
ucv_stringbuf_addstr(*buf, str, next - str);
|
|
}
|
|
|
|
static void
|
|
uc_uline_add_pos(uc_vm_t *vm, uc_value_t *list, ssize_t start, ssize_t end)
|
|
{
|
|
uc_value_t *val = ucv_array_new(vm);
|
|
ucv_array_push(val, ucv_int64_new(start));
|
|
ucv_array_push(val, ucv_int64_new(end));
|
|
ucv_array_push(list, val);
|
|
}
|
|
|
|
static uc_value_t *
|
|
uc_uline_parse_args(uc_vm_t *vm, size_t nargs, bool check)
|
|
{
|
|
struct uc_arg_parser *argp = uc_fn_thisval("uline.argp");
|
|
uc_value_t *list = NULL, *pos_list = NULL;
|
|
uc_value_t *args = NULL, *pos_args = NULL;
|
|
uc_value_t *str_arg = uc_fn_arg(0);
|
|
uc_stringbuf_t *buf = NULL;
|
|
uc_value_t *missing = NULL;
|
|
uc_value_t *ret;
|
|
const char *start, *str, *end;
|
|
ssize_t start_idx = -1, end_idx = 0;
|
|
enum {
|
|
UNQUOTED,
|
|
BACKSLASH,
|
|
SINGLE_QUOTE,
|
|
DOUBLE_QUOTE,
|
|
DOUBLE_QUOTE_BACKSLASH,
|
|
} state = UNQUOTED;
|
|
static const char * const state_str[] = {
|
|
[BACKSLASH] = "\\",
|
|
[SINGLE_QUOTE] = "'",
|
|
[DOUBLE_QUOTE] = "\"",
|
|
[DOUBLE_QUOTE_BACKSLASH] = "\\\"",
|
|
};
|
|
#define UNQUOTE_TOKENS " \t\r\n'\"\\"
|
|
char unquote_tok[] = UNQUOTE_TOKENS "\x00";
|
|
unquote_tok[strlen(UNQUOTE_TOKENS)] = argp->line_sep;
|
|
|
|
if (!argp || ucv_type(str_arg) != UC_STRING)
|
|
return NULL;
|
|
|
|
if (!check) {
|
|
list = ucv_array_new(vm);
|
|
pos_list = ucv_array_new(vm);
|
|
if (argp->line_sep) {
|
|
args = ucv_array_new(vm);
|
|
pos_args = ucv_array_new(vm);
|
|
ucv_array_push(args, list);
|
|
ucv_array_push(pos_args, pos_list);
|
|
} else {
|
|
args = list;
|
|
pos_args = pos_list;
|
|
}
|
|
}
|
|
|
|
start = str = ucv_string_get(str_arg);
|
|
end = str + ucv_string_length(str_arg);
|
|
skip_space(&str, end);
|
|
|
|
while (*str && str < end) {
|
|
const char *next;
|
|
|
|
switch (state) {
|
|
case UNQUOTED:
|
|
if (isspace(*str)) {
|
|
skip_space(&str, end);
|
|
if (!buf)
|
|
continue;
|
|
|
|
ucv_array_push(list, ucv_stringbuf_finish(buf));
|
|
uc_uline_add_pos(vm, pos_list, start_idx, end_idx);
|
|
start_idx = -1;
|
|
buf = NULL;
|
|
continue;
|
|
}
|
|
|
|
if (start_idx < 0)
|
|
start_idx = str - start;
|
|
next = str + strcspn(str, unquote_tok);
|
|
if (list)
|
|
add_str(&buf, str, next);
|
|
str = next;
|
|
end_idx = str - start;
|
|
|
|
switch (*str) {
|
|
case 0:
|
|
continue;
|
|
case '\'':
|
|
state = SINGLE_QUOTE;
|
|
break;
|
|
case '"':
|
|
state = DOUBLE_QUOTE;
|
|
break;
|
|
case '\\':
|
|
state = BACKSLASH;
|
|
break;
|
|
default:
|
|
if (argp->line_sep &&
|
|
*str == argp->line_sep) {
|
|
str++;
|
|
if (list) {
|
|
if (buf) {
|
|
ucv_array_push(list, ucv_stringbuf_finish(buf));
|
|
uc_uline_add_pos(vm, pos_list, start_idx, end_idx);
|
|
start_idx = -1;
|
|
}
|
|
|
|
buf = NULL;
|
|
list = ucv_array_new(vm);
|
|
ucv_array_push(args, list);
|
|
|
|
pos_list = ucv_array_new(vm);
|
|
ucv_array_push(pos_args, pos_list);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
if (!buf)
|
|
buf = ucv_stringbuf_new();
|
|
str++;
|
|
break;
|
|
|
|
case BACKSLASH:
|
|
case DOUBLE_QUOTE_BACKSLASH:
|
|
if (start_idx < 0)
|
|
start_idx = str - start;
|
|
if (list && *str != '\n')
|
|
add_str(&buf, str, str + 1);
|
|
str++;
|
|
state--;
|
|
end_idx = str - start;
|
|
break;
|
|
|
|
case SINGLE_QUOTE:
|
|
if (start_idx < 0)
|
|
start_idx = str - start;
|
|
next = str + strcspn(str, "'");
|
|
if (list)
|
|
add_str(&buf, str, next);
|
|
str = next;
|
|
|
|
if (*str == '\'') {
|
|
state = UNQUOTED;
|
|
str++;
|
|
}
|
|
end_idx = str - start;
|
|
break;
|
|
|
|
case DOUBLE_QUOTE:
|
|
if (start_idx < 0)
|
|
start_idx = str - start;
|
|
next = str + strcspn(str, "\"\\");
|
|
if (list)
|
|
add_str(&buf, str, next);
|
|
str = next;
|
|
|
|
if (*str == '"') {
|
|
state = UNQUOTED;
|
|
str++;
|
|
} else if (*str == '\\') {
|
|
state = DOUBLE_QUOTE_BACKSLASH;
|
|
str++;
|
|
}
|
|
end_idx = str - start;
|
|
}
|
|
}
|
|
|
|
if (buf) {
|
|
ucv_array_push(list, ucv_stringbuf_finish(buf));
|
|
uc_uline_add_pos(vm, pos_list, start_idx, end_idx);
|
|
}
|
|
|
|
if (state_str[state])
|
|
missing = ucv_string_new(state_str[state]);
|
|
|
|
if (!list)
|
|
return missing;
|
|
|
|
ret = ucv_object_new(vm);
|
|
ucv_object_add(ret, "args", args);
|
|
ucv_object_add(ret, "pos", pos_args);
|
|
if (missing)
|
|
ucv_object_add(ret, "missing", missing);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static uc_value_t *
|
|
uc_uline_arg_parser(uc_vm_t *vm, size_t nargs)
|
|
{
|
|
uc_value_t *opts = uc_fn_arg(0);
|
|
struct uc_arg_parser *argp;
|
|
uc_value_t *a;
|
|
char sep = 0;
|
|
|
|
if ((a = ucv_object_get(opts, "line_separator", NULL)) != NULL) {
|
|
if (ucv_type(a) != UC_STRING || ucv_string_length(a) != 1)
|
|
return NULL;
|
|
|
|
sep = ucv_string_get(a)[0];
|
|
}
|
|
|
|
argp = calloc(1, sizeof(*argp));
|
|
argp->line_sep = sep;
|
|
|
|
return ucv_resource_new(argp_type, argp);
|
|
}
|
|
|
|
static uc_value_t *
|
|
uc_uline_argp_parse(uc_vm_t *vm, size_t nargs)
|
|
{
|
|
return uc_uline_parse_args(vm, nargs, false);
|
|
}
|
|
|
|
static uc_value_t *
|
|
uc_uline_argp_check(uc_vm_t *vm, size_t nargs)
|
|
{
|
|
return uc_uline_parse_args(vm, nargs, true);
|
|
}
|
|
|
|
static uc_value_t *
|
|
uc_uline_argp_escape(uc_vm_t *vm, size_t nargs)
|
|
{
|
|
uc_value_t *arg = uc_fn_arg(0);
|
|
uc_value_t *ref_arg = uc_fn_arg(1);
|
|
const char *str, *next;
|
|
uc_stringbuf_t *buf;
|
|
char ref = 0;
|
|
|
|
if (ucv_type(arg) != UC_STRING)
|
|
return NULL;
|
|
|
|
if (ucv_type(ref_arg) == UC_STRING)
|
|
ref = ucv_string_get(ref_arg)[0];
|
|
|
|
str = ucv_string_get(arg);
|
|
if (ref != '"' && ref != '\'') {
|
|
next = str + strcspn(str, "\n\t '\"");
|
|
if (*next)
|
|
ref = '"';
|
|
}
|
|
if (ref != '"' && ref != '\'')
|
|
return ucv_string_new(str);
|
|
|
|
buf = ucv_stringbuf_new();
|
|
ucv_stringbuf_addstr(buf, &ref, 1);
|
|
|
|
while (*str) {
|
|
next = strchr(str, ref);
|
|
if (!next) {
|
|
ucv_stringbuf_addstr(buf, str, strlen(str));
|
|
break;
|
|
}
|
|
|
|
if (next - str)
|
|
ucv_stringbuf_addstr(buf, str, next - str);
|
|
if (ref == '\'')
|
|
ucv_stringbuf_addstr(buf, "'\\''", 4);
|
|
else
|
|
ucv_stringbuf_addstr(buf, "\\\"", 2);
|
|
str = next + 1;
|
|
}
|
|
|
|
ucv_stringbuf_addstr(buf, &ref, 1);
|
|
|
|
return ucv_stringbuf_finish(buf);
|
|
}
|
|
|
|
static uc_value_t *
|
|
uc_uline_getpass(uc_vm_t *vm, size_t nargs)
|
|
{
|
|
uc_value_t *prompt = uc_fn_arg(0);
|
|
char *pw;
|
|
|
|
if (ucv_type(prompt) != UC_STRING)
|
|
return NULL;
|
|
|
|
pw = getpass(ucv_string_get(prompt));
|
|
if (!pw)
|
|
return NULL;
|
|
|
|
return ucv_string_new(pw);
|
|
}
|
|
|
|
static const uc_function_list_t argp_fns[] = {
|
|
{ "parse", uc_uline_argp_parse },
|
|
{ "check", uc_uline_argp_check },
|
|
{ "escape", uc_uline_argp_escape },
|
|
};
|
|
|
|
static const uc_function_list_t state_fns[] = {
|
|
{ "close", uc_uline_close },
|
|
{ "poll", uc_uline_poll },
|
|
{ "poll_stop", uc_uline_poll_stop },
|
|
{ "poll_key", uc_uline_poll_key },
|
|
{ "reset_key_input", uc_uline_reset_key_input },
|
|
{ "get_line", uc_uline_get_line },
|
|
{ "get_window", uc_uline_get_window },
|
|
{ "set_hint", uc_uline_set_hint },
|
|
{ "set_state", uc_uline_set_state },
|
|
{ "set_uloop", uc_uline_set_uloop },
|
|
{ "hide_prompt", uc_uline_hide_prompt },
|
|
{ "refresh_prompt", uc_uline_refresh_prompt },
|
|
};
|
|
|
|
static const uc_function_list_t global_fns[] = {
|
|
{ "new", uc_uline_new },
|
|
{ "arg_parser", uc_uline_arg_parser },
|
|
{ "getpass", uc_uline_getpass },
|
|
};
|
|
|
|
void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
|
|
{
|
|
uc_function_list_register(scope, global_fns);
|
|
|
|
state_type = uc_type_declare(vm, "uline.state", state_fns, free_state);
|
|
argp_type = uc_type_declare(vm, "uline.argp", argp_fns, free);
|
|
registry = ucv_array_new(vm);
|
|
uc_vm_registry_set(vm, "uline.registry", registry);
|
|
}
|