// SPDX-FileCopyrightText: 2009-2021 nibble <nibble.ds@gmail.com>
// SPDX-FileCopyrightText: 2009-2021 pancake <pancake@nopcode.org>
// SPDX-License-Identifier: LGPL-3.0-only

#include <rz_util/rz_assert.h>
#include <rz_util/rz_file.h>
#include <rz_util/rz_log.h>
#include <rz_util/rz_str.h>
#include <rz_util/rz_sys.h>
#define INTERACTIVE_MAX_REP 1024

#include <rz_core.h>
#include <rz_types.h>
#include <rz_analysis.h>
#include <rz_cons.h>
#include <rz_cmd.h>
#include <rz_windows.h>
#include <stdint.h>
#include <sys/types.h>
#include <ctype.h>
#include <stdarg.h>
#if __UNIX__
#include <sys/utsname.h>
#endif

#include <cmd_descs.h>

#include <tree_sitter/api.h>
TSLanguage *tree_sitter_rzcmd();

#include "cmd_debug.c"
#include "cmd_analysis.c"
#include "cmd_magic.c"
#include "cmd_search.c" // defines incDigitBuffer... used by cmd_print
#include "cmd_print.c"
#include "cmd_math.c"

RZ_API void rz_core_cmd_help(const RzCore *core, const char *help[]) {
	rz_cons_cmd_help(help, core->print->flags & RZ_PRINT_FLAGS_COLOR);
}

struct duplicate_flag_t {
	RzList /*<RzFlagItem *>*/ *ret;
	const char *word;
};

static bool duplicate_flag(RzFlagItem *flag, void *u) {
	struct duplicate_flag_t *user = (struct duplicate_flag_t *)u;
	/* filter per flag spaces */
	if (!user->word || rz_str_glob(flag->name, user->word)) {
		RzFlagItem *cloned_item = rz_flag_item_clone(flag);
		if (!cloned_item) {
			return false;
		}
		rz_list_append(user->ret, cloned_item);
	}
	return true;
}

RZ_IPI bool rz_core_cmd_lastcmd_repeat(RzCore *core, bool next) {
	int res = -1;
	// Fix for backtickbug px`~`
	if (!core->lastcmd || core->cons->context->cmd_depth < 1) {
		return false;
	}
	switch (*core->lastcmd) {
	case '.':
		if (core->lastcmd[1] == '(') { // macro call
			res = rz_core_cmd0(core, core->lastcmd);
		}
		break;
	case 'd': // debug
		res = rz_core_cmd0(core, core->lastcmd);
		switch (core->lastcmd[1]) {
		case 's':
		case 'c':
			rz_core_seek_to_register(core, "PC", false);
			rz_core_cmd0(core, "pd 1");
		}
		break;
	case 'p': // print
	case 'x':
	case '$':
		if (!strncmp(core->lastcmd, "pd", 2)) {
			if (core->lastcmd[2] == ' ') {
				ut64 addr = rz_num_math(core->num, core->lastcmd + 3);
				rz_core_seek_opcode(core, addr, false);
			} else {
				rz_core_cmd0(core, "so `pi~?`");
			}
		} else {
			if (next) {
				rz_core_seek(core, core->offset + core->blocksize, true);
			} else {
				if (core->blocksize > core->offset) {
					rz_core_seek(core, 0, true);
				} else {
					rz_core_seek(core, core->offset - core->blocksize, true);
				}
			}
		}
		res = rz_core_cmd0(core, core->lastcmd);
		break;
	}
	core->is_lastcmd = true;
	return res != -1;
}

static int rz_core_cmd_nullcallback(void *data) {
	RzCore *core = (RzCore *)data;
	if (core->cons->context->breaked) {
		core->cons->context->breaked = false;
		return 0;
	}
	if (!core->cmdrepeat) {
		return 0;
	}
	rz_core_cmd_lastcmd_repeat(core, true);
	return 1;
}

static int lang_run_file(RzCore *core, RzLang *lang, const char *file) {
	rz_core_sysenv_begin(core);
	return rz_lang_run_file(core->lang, file);
}

static char *langFromHashbang(RzCore *core, const char *file) {
	int fd = rz_sys_open(file, O_RDONLY, 0);
	if (fd != -1) {
		char firstLine[128] = { 0 };
		int len = read(fd, (ut8 *)firstLine, sizeof(firstLine) - 1);
		if (len < 1) {
			close(fd);
			return NULL;
		}
		firstLine[len] = 0;
		if (!strncmp(firstLine, "#!/", 3)) {
			// I CAN HAS A HASHBANG
			char *nl = strchr(firstLine, '\n');
			if (nl) {
				*nl = 0;
			}
			nl = strchr(firstLine, ' ');
			if (nl) {
				*nl = 0;
			}
			nl = rz_str_dup(firstLine + 2);
			close(fd);
			return nl;
		}
		close(fd);
	}
	return NULL;
}

/* \brief Run the script defined by path in \p file. Could be a Rizin script, or rz-pipe one.
 */
RZ_API bool rz_core_run_script(RzCore *core, RZ_NONNULL const char *file) {
	rz_return_val_if_fail(file, false);
	bool ret = false;
	RzListIter *iter;
	RzLangPlugin *p;
	char *name;

	rz_list_foreach (core->scriptstack, iter, name) {
		if (!strcmp(file, name)) {
			RZ_LOG_WARN("Ignored nested source: '%s'\n", file);
			return false;
		}
	}
	rz_list_push(core->scriptstack, rz_str_dup(file));

	if (!strcmp(file, "-")) {
		char *out = rz_core_editor(core, NULL, NULL);
		if (out) {
			ret = rz_core_cmd_lines(core, out);
			free(out);
		}
	} else if (rz_str_endswith(file, ".html")) {
		char *httpIndex = rz_str_dup(rz_config_get(core->config, "http.index"));
		char *absfile = rz_file_abspath(file);
		rz_config_set(core->config, "http.index", absfile);
		free(absfile);
		rz_core_rtr_http(core, true);
		rz_config_set(core->config, "http.index", httpIndex);
		free(httpIndex);
		ret = true;
	} else if (rz_str_endswith(file, ".c")) {
		rz_core_cmd_strf(core, "#!c %s", file);
		ret = true;
	} else if (rz_file_is_c(file)) {
		const char *dir = rz_config_get(core->config, "dir.types");
		char *error_msg = NULL;
		RzTypeDB *typedb = rz_analysis_get_type_db(core->analysis);
		int result = rz_type_parse_file(typedb, file, dir, &error_msg);
		if (error_msg) {
			rz_str_trim_tail(error_msg);
			RZ_LOG_ERROR("core: %s\n", error_msg);
			free(error_msg);
		}
		ret = result != 0;
	} else {
		p = rz_lang_get_by_extension(core->lang, file);
		if (p) {
			rz_lang_use(core->lang, p->name);
			ret = lang_run_file(core, core->lang, file);
		} else {
// XXX this is an ugly hack, we need to use execve here and specify args properly
#if __WINDOWS__
#define cmdstr(x) rz_str_newf(x " %s", file);
#else
#define cmdstr(x) rz_str_newf(x " '%s'", file);
#endif
			const char *p = rz_str_lchr(file, '.');
			if (p) {
				const char *ext = p + 1;
				/* TODO: handle this inside rz_lang_pipe with new APIs */
				if (!strcmp(ext, "js")) {
					char *cmd = cmdstr("node");
					rz_lang_use(core->lang, "pipe");
					lang_run_file(core, core->lang, cmd);
					free(cmd);
					ret = 1;
				} else if (!strcmp(ext, "exe")) {
#if __WINDOWS__
					char *cmd = rz_str_newf("%s", file);
#else
					char *cmd = cmdstr("wine");
#endif
					rz_lang_use(core->lang, "pipe");
					lang_run_file(core, core->lang, cmd);
					free(cmd);
					ret = 1;
				} else if (!strcmp(ext, "zig")) {
					char *cmd = cmdstr("zig run");
					rz_lang_use(core->lang, "pipe");
					lang_run_file(core, core->lang, cmd);
					free(cmd);
					ret = 1;
				} else if (!strcmp(ext, "d")) {
					char *cmd = cmdstr("dmd -run");
					rz_lang_use(core->lang, "pipe");
					lang_run_file(core, core->lang, cmd);
					free(cmd);
					ret = 1;
				} else if (!strcmp(ext, "go")) {
					char *cmd = cmdstr("go run");
					rz_lang_use(core->lang, "pipe");
					lang_run_file(core, core->lang, cmd);
					free(cmd);
					ret = 1;
				} else if (!strcmp(ext, "rb")) {
					char *cmd = cmdstr("ruby");
					rz_lang_use(core->lang, "pipe");
					lang_run_file(core, core->lang, cmd);
					free(cmd);
					ret = 1;
				} else if (!strcmp(ext, "vala")) {
					rz_lang_use(core->lang, "vala");
					lang_run_file(core, core->lang, file);
					ret = 1;
				} else if (!strcmp(ext, "sh")) {
					char *shell = rz_sys_getenv("SHELL");
					if (!shell) {
						shell = rz_str_dup("sh");
					}
					if (shell) {
						rz_lang_use(core->lang, "pipe");
						char *cmd = rz_str_newf("%s '%s'", shell, file);
						if (cmd) {
							lang_run_file(core, core->lang, cmd);
							free(cmd);
						}
						free(shell);
					}
					ret = 1;
				} else if (!strcmp(ext, "pl")) {
					char *cmd = cmdstr("perl");
					rz_lang_use(core->lang, "pipe");
					lang_run_file(core, core->lang, cmd);
					free(cmd);
					ret = 1;
				} else if (!strcmp(ext, "py")) {
					char *cmd = cmdstr("python");
					rz_lang_use(core->lang, "pipe");
					lang_run_file(core, core->lang, cmd);
					free(cmd);
					ret = 1;
				}
			} else {
				char *abspath = rz_file_path(file);
				char *lang = langFromHashbang(core, file);
				if (lang) {
					rz_lang_use(core->lang, "pipe");
					char *cmd = rz_str_newf("%s '%s'", lang, file);
					lang_run_file(core, core->lang, cmd);
					free(lang);
					free(cmd);
					ret = 1;
				}
				free(abspath);
			}
			if (!ret) {
				ret = rz_core_cmd_file(core, file);
			}
		}
	}
	free(rz_list_pop(core->scriptstack));
	return ret;
}

RZ_IPI int rz_line_hist_sdb_up(RzLine *line) {
	rz_return_val_if_fail(line && line->sdbshell_hist_iter, false);
	if (!rz_list_next(line->sdbshell_hist_iter)) {
		return false;
	}
	line->sdbshell_hist_iter = rz_list_next(line->sdbshell_hist_iter);
	strncpy(line->buffer.data, rz_list_val(line->sdbshell_hist_iter), RZ_LINE_BUFSIZE - 1);
	line->buffer.index = line->buffer.length = strlen(line->buffer.data);
	return true;
}

RZ_IPI int rz_line_hist_sdb_down(RzLine *line) {
	rz_return_val_if_fail(line && line->sdbshell_hist_iter, false);
	if (!rz_list_prev(line->sdbshell_hist_iter)) {
		return false;
	}
	line->sdbshell_hist_iter = rz_list_prev(line->sdbshell_hist_iter);
	strncpy(line->buffer.data, rz_list_val(line->sdbshell_hist_iter), RZ_LINE_BUFSIZE - 1);
	line->buffer.index = line->buffer.length = strlen(line->buffer.data);
	return true;
}

RZ_IPI void rz_core_kuery_print(RzCore *core, const char *k) {
	char *out = sdb_querys(core->sdb, NULL, 0, k);
	if (out) {
		rz_cons_print(out);
	}
	free(out);
}

RZ_IPI RzCmdStatus rz_push_escaped_handler(RzCore *core, int argc, const char **argv) {
	char *input = rz_str_array_join(argv + 1, argc - 1, " ");
	int len = rz_str_unescape(input);
	RzCmdStatus res = bool2status(rz_cons_readpush(input, len));
	free(input);
	return res;
}

static RzCmdStatus pointer_read(RzCore *core, const char *expr) {
	ut64 n = rz_num_math(core->num, expr);
	if (core->num->dbz) {
		RZ_LOG_ERROR("core: RzNum ERROR: Division by Zero\n");
		return RZ_CMD_STATUS_ERROR;
	}
	const bool big_endian = rz_asm_is_big_endian_set(core->rasm);
	int arch_bits = rz_asm_get_bits(core->rasm);
	if (!rz_io_read_i(core->io, n, &n, arch_bits / 8, big_endian)) {
		return RZ_CMD_STATUS_ERROR;
	}
	rz_cons_printf("0x%" PFMT64x "\n", n);
	return RZ_CMD_STATUS_OK;
}

static RzCmdStatus pointer_write(RzCore *core, const char *addr_arg, const char *value_arg) {
	bool ok;
	ut64 addr = rz_num_math(core->num, addr_arg);
	if (core->num->nc.errors) {
		RZ_LOG_ERROR("Could not convert address argument to number\n");
		return RZ_CMD_STATUS_ERROR;
	}

	if (rz_hex_str_is_valid(value_arg, false) > 0) {
		// write a byte sequence
		ok = rz_core_write_hexpair(core, addr, value_arg) > 0;
	} else {
		// write a numerical value
		ut64 value = rz_num_math(core->num, value_arg);
		if (core->num->nc.errors) {
			RZ_LOG_ERROR("Could not convert value argument to number\n");
			return RZ_CMD_STATUS_ERROR;
		}

		ok = rz_core_write_value_at(core, addr, value, rz_asm_get_bits(core->rasm) / 8);
	}

	return bool2status(ok);
}

RZ_IPI RzCmdStatus rz_pointer_handler(RzCore *core, int argc, const char **argv) {
	switch (argc) {
	case 2:
		return pointer_read(core, argv[1]);
	case 3:
		return pointer_write(core, argv[1], argv[2]);
	default:
		return RZ_CMD_STATUS_WRONG_ARGS;
	}
}

RZ_IPI RzCmdStatus rz_last_output_handler(RzCore *core, int argc, const char **argv) {
	rz_cons_last();
	return RZ_CMD_STATUS_OK;
}

#if __WINDOWS__
#include <tchar.h>
#define __CLOSE_DUPPED_PIPES() \
	close(1); \
	close(fd_out); \
	fd_out = -1;

static void rz_w32_cmd_pipe(RzCore *core, char *rizin_cmd, char *shell_cmd) {
	STARTUPINFO si = { 0 };
	PROCESS_INFORMATION pi = { 0 };
	SECURITY_ATTRIBUTES sa;
	HANDLE pipe[2] = { NULL, NULL };
	int fd_out = -1, cons_out = -1;
	char *_shell_cmd = NULL;
	LPTSTR _shell_cmd_ = NULL;
	DWORD mode;
	TCHAR *systemdir = NULL;
	GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &mode);

	sa.nLength = sizeof(SECURITY_ATTRIBUTES);
	sa.bInheritHandle = TRUE;
	sa.lpSecurityDescriptor = NULL;
	if (!CreatePipe(&pipe[0], &pipe[1], &sa, 0)) {
		rz_sys_perror("rz_w32_cmd_pipe/CreatePipe");
		goto err_r_w32_cmd_pipe;
	}
	if (!SetHandleInformation(pipe[1], HANDLE_FLAG_INHERIT, 0)) {
		rz_sys_perror("rz_w32_cmd_pipe/SetHandleInformation");
		goto err_r_w32_cmd_pipe;
	}
	si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
	si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	si.hStdInput = pipe[0];
	si.dwFlags |= STARTF_USESTDHANDLES;
	si.cb = sizeof(si);
	_shell_cmd = shell_cmd;
	while (*_shell_cmd && isspace((ut8)*_shell_cmd)) {
		_shell_cmd++;
	}
	char *tmp = rz_str_newf("/Q /c \"%s\"", _shell_cmd);
	if (!tmp) {
		goto err_r_w32_cmd_pipe;
	}
	_shell_cmd = tmp;
	_shell_cmd_ = rz_sys_conv_utf8_to_win(_shell_cmd);
	free(tmp);
	if (!_shell_cmd_) {
		goto err_r_w32_cmd_pipe;
	}
	systemdir = calloc(MAX_PATH, sizeof(TCHAR));
	if (!systemdir) {
		goto err_r_w32_cmd_pipe;
	}
	int ret = GetSystemDirectory(systemdir, MAX_PATH);
	if (!ret) {
		rz_sys_perror("rz_w32_cmd_pipe/systemdir");
		goto err_r_w32_cmd_pipe;
	}
	_tcscat_s(systemdir, MAX_PATH, TEXT("\\cmd.exe"));
	// exec windows process
	if (!CreateProcess(systemdir, _shell_cmd_, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
		rz_sys_perror("rz_w32_cmd_pipe/CreateProcess");
		goto err_r_w32_cmd_pipe;
	}
	fd_out = _open_osfhandle((intptr_t)pipe[1], _O_WRONLY | _O_TEXT);
	if (fd_out == -1) {
		perror("_open_osfhandle");
		goto err_r_w32_cmd_pipe;
	}
	cons_out = dup(1);
	dup2(fd_out, 1);
	// exec rizin command
	rz_core_cmd(core, rizin_cmd, 0);

	HANDLE th = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)rz_cons_flush, NULL, 0, NULL);
	if (!th) {
		__CLOSE_DUPPED_PIPES();
		goto err_r_w32_cmd_pipe;
	}
	while (true) {
		int ret = WaitForSingleObject(th, 50);
		if (!ret) {
			// Successfully written everything to pipe
			__CLOSE_DUPPED_PIPES();
			WaitForSingleObject(pi.hProcess, INFINITE);
			break;
		}
		ret = WaitForSingleObject(pi.hProcess, 50);
		if (!ret) {
			// Process exited before we finished writing to pipe
			DWORD exit;
			if (GetExitCodeThread(th, &exit) && exit == STILL_ACTIVE) {
				CancelSynchronousIo(th);
			}
			WaitForSingleObject(th, INFINITE);
			__CLOSE_DUPPED_PIPES();
			break;
		}
	}
	CloseHandle(th);
err_r_w32_cmd_pipe:
	if (pi.hProcess) {
		CloseHandle(pi.hProcess);
	}
	if (pi.hThread) {
		CloseHandle(pi.hThread);
	}
	if (pipe[0]) {
		CloseHandle(pipe[0]);
	}
	if (fd_out != -1) {
		close(fd_out);
	}
	if (cons_out != -1) {
		dup2(cons_out, 1);
		close(cons_out);
	}
	free(systemdir);
	free(_shell_cmd_);
	SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), mode);
}
#undef __CLOSE_DUPPED_PIPES
#endif

static bool set_tmp_arch(RzCore *core, char *arch, char **tmparch) {
	rz_return_val_if_fail(tmparch, false);
	*tmparch = rz_str_dup(rz_config_get(core->config, "asm.arch"));
	rz_config_set(core->config, "asm.arch", arch);
	core->fixedarch = true;
	return true;
}

static bool set_tmp_bits(RzCore *core, int bits, char **tmpbits, int *cmd_ignbithints) {
	rz_return_val_if_fail(tmpbits, false);
	*tmpbits = rz_str_dup(rz_config_get(core->config, "asm.bits"));
	rz_config_set_i(core->config, "asm.bits", bits);
	core->fixedbits = true;
	// XXX: why?
	*cmd_ignbithints = rz_config_get_i(core->config, "analysis.ignbithints");
	rz_config_set_i(core->config, "analysis.ignbithints", 1);
	return true;
}

struct exec_command_t {
	RzCore *core;
	const char *cmd;
};

static int bb_cmp(const void *a, const void *b, void *user) {
	const RzAnalysisBlock *ba = a;
	const RzAnalysisBlock *bb = b;
	return ba->addr - bb->addr;
}

struct tsr2cmd_state {
	TSParser *parser;
	RzCore *core;
	char *input;
	RzPVector /*<char *>*/ saved_input;
	TSTree *tree;
	RzPVector /*<TSTree *>*/ saved_tree;
	bool log;
	bool split_lines;
	TSNode substitute_cmd;
};

struct tsr2cmd_edit {
	char *new_text;
	char *old_text;
	ut32 start;
	ut32 end;
	TSPoint start_point;
	TSPoint end_point;
};

typedef RzCmdStatus (*ts_handler)(struct tsr2cmd_state *state, TSNode node);

struct ts_data_symbol_map {
	const char *name;
	void *data;
};

#define TS_START_END(node, start, end) \
	do { \
		start = ts_node_start_byte(node); \
		end = ts_node_end_byte(node); \
	} while (0)

static char *ts_node_sub_string(TSNode node, const char *cstr) {
	ut32 start, end;
	TS_START_END(node, start, end);
	return rz_str_newf("%.*s", end - start, cstr + start);
}

static char *ts_node_sub_parent_string(TSNode parent, TSNode node, const char *cstr) {
	ut32 start, end;
	TS_START_END(node, start, end);
	ut32 parent_start = ts_node_start_byte(parent);
	start -= parent_start;
	end -= parent_start;
	return rz_str_newf("%.*s", end - start, cstr + start);
}

#define DEFINE_SYMBOL_TS_FCN(name) TSSymbol ts_##name##_symbol

#define DEFINE_IS_TS_FCN(name) \
	static inline bool is_ts_##name(TSNode node) { \
		return ts_node_symbol(node) == ts_##name##_symbol; \
	}

#define DEFINE_IS_TS_FCN_AND_SYMBOL(name) \
	DEFINE_SYMBOL_TS_FCN(name); \
	DEFINE_IS_TS_FCN(name)

#define DEFINE_HANDLE_TS_FCN(name) \
	static RzCmdStatus handle_ts_##name##_internal(struct tsr2cmd_state *state, TSNode node, char *node_string); \
	static RzCmdStatus handle_ts_##name(struct tsr2cmd_state *state, TSNode node) { \
		char *node_string = ts_node_sub_string(node, state->input); \
		RZ_LOG_DEBUG(#name ": '%s'\n", node_string); \
		RzCmdStatus res = handle_ts_##name##_internal(state, node, node_string); \
		free(node_string); \
		return res; \
	} \
	static RzCmdStatus handle_ts_##name##_internal(struct tsr2cmd_state *state, TSNode node, char *node_string)

#define DEFINE_HANDLE_TS_FCN_AND_SYMBOL(name) \
	DEFINE_SYMBOL_TS_FCN(name); \
	DEFINE_HANDLE_TS_FCN(name)

#define UPDATE_CMD_STATUS_RES(res, cmd_res, label) \
	if ((cmd_res) != RZ_CMD_STATUS_OK) { \
		res = (cmd_res); \
		goto label; \
	}

static RzCmdStatus handle_ts_stmt(struct tsr2cmd_state *state, TSNode node);
static RzCmdStatus handle_ts_stmt_tmpseek(struct tsr2cmd_state *state, TSNode node);
static RzCmdStatus core_cmd_tsrzcmd(RzCore *core, const char *cstr, bool split_lines, bool log);

// Piping commands and fallbacks.
static PipeFallbacks internal_commands[] = {
	{ "uniq", rz_syscmd_uniq_pipe },
	{ "sort", rz_syscmd_sort_pipe }
};

static char *system_exec_stdin(bool is_pipe, int argc, char **argv, const ut8 *input, int input_len, int *length) {

	for (size_t i = 0; i < RZ_ARRAY_SIZE(internal_commands); i++) {
		if (RZ_STR_EQ(argv[0], internal_commands[i].command)) {
			return internal_commands[i].fallback_fn((const char *)input, length);
		}
	}

	char *output = NULL;
	if (!rz_subprocess_init()) {
		RZ_LOG_ERROR("Cannot initialize subprocess.\n");
		return NULL;
	}

	RzSubprocessOpt opt = {
		.file = argv[0],
		.args = (const char **)&argv[1],
		.args_size = argc - 1,
		.envvars = NULL,
		.envvals = NULL,
		.env_size = 0,
		.stdin_pipe = RZ_SUBPROCESS_PIPE_CREATE,
		.stdout_pipe = is_pipe ? RZ_SUBPROCESS_PIPE_CREATE : RZ_SUBPROCESS_PIPE_NONE,
		.stderr_pipe = is_pipe ? RZ_SUBPROCESS_PIPE_STDOUT : RZ_SUBPROCESS_PIPE_NONE,
	};

	RzSubprocess *proc = rz_subprocess_start_opt(&opt);
	if (!proc) {
		RZ_LOG_ERROR("Cannot start subprocess.\n");
		rz_subprocess_fini();
		return NULL;
	}

	rz_subprocess_stdin_write(proc, input, input_len);
	rz_subprocess_wait(proc, UT64_MAX);
	rz_subprocess_ret(proc);

	output = (char *)rz_subprocess_out(proc, length);
	rz_subprocess_free(proc);
	rz_subprocess_fini();

	return output;
}

static ut8 *core_cmd_raw_node(RzCore *core, struct tsr2cmd_state *state, TSNode rizin_cmd, int *length) {
	const char *static_str;
	ut8 *retstr = NULL;
	rz_cons_push();
	if (handle_ts_stmt(state, rizin_cmd) != RZ_CMD_STATUS_OK) {
		rz_cons_pop();
		return NULL;
	}
	rz_cons_filter();

	static_str = rz_cons_get_buffer();
	int len = rz_cons_get_buffer_len();
	retstr = (ut8 *)rz_str_newlen(static_str, len);
	*length = len;

	rz_cons_pop();
	rz_cons_echo(NULL);
	return retstr;
}

/**
 * \brief Executes a rizin command and pipes the result to the stdin of the program specified in argc/argv
 *
 * Executes a rizin command specified in \p rizin_cmd and pipe its stdout to the
 * stdin of the system program specified in \p argc and \p argv arguments.
 *
 * The output of the second program is then sent into RzCons.
 * */
static RzCmdStatus core_cmd_pipe(RzCore *core, struct tsr2cmd_state *state, TSNode rizin_cmd, int argc, char **argv) {
	int length = 0;
	bool is_pipe = core->is_pipe;
	core->is_pipe = true;
	ut8 *bytes = core_cmd_raw_node(core, state, rizin_cmd, &length);
	core->is_pipe = is_pipe;
	if (!bytes) {
		return RZ_CMD_STATUS_ERROR;
	}

	char *out = system_exec_stdin(core->is_pipe, argc, argv, bytes, length, &length);
	if (out) {
		rz_cons_memcat(out, length);
	}

	free(bytes);
	free(out);
	return RZ_CMD_STATUS_OK;
}

DEFINE_IS_TS_FCN_AND_SYMBOL(fdn_redirect_operator)
DEFINE_IS_TS_FCN_AND_SYMBOL(fdn_append_operator)
DEFINE_IS_TS_FCN_AND_SYMBOL(html_redirect_operator)
DEFINE_IS_TS_FCN_AND_SYMBOL(html_append_operator)
DEFINE_IS_TS_FCN_AND_SYMBOL(cmd_substitution_arg)
DEFINE_IS_TS_FCN_AND_SYMBOL(args)
DEFINE_IS_TS_FCN_AND_SYMBOL(arg)
DEFINE_IS_TS_FCN_AND_SYMBOL(arg_identifier)
DEFINE_IS_TS_FCN_AND_SYMBOL(pf_arg)
DEFINE_IS_TS_FCN_AND_SYMBOL(pf_args)
DEFINE_IS_TS_FCN_AND_SYMBOL(pf_dot_cmd_args)
DEFINE_IS_TS_FCN_AND_SYMBOL(pf_new_args)
DEFINE_IS_TS_FCN_AND_SYMBOL(pf_concatenation)
DEFINE_IS_TS_FCN_AND_SYMBOL(double_quoted_arg)
DEFINE_IS_TS_FCN_AND_SYMBOL(single_quoted_arg)
DEFINE_IS_TS_FCN_AND_SYMBOL(concatenation)
DEFINE_IS_TS_FCN_AND_SYMBOL(grep_specifier)
DEFINE_IS_TS_FCN_AND_SYMBOL(statements)

static struct tsr2cmd_edit *create_cmd_edit(struct tsr2cmd_state *state, TSNode arg, char *new_text) {
	struct tsr2cmd_edit *e = RZ_NEW0(struct tsr2cmd_edit);
	ut32 command_start = ts_node_start_byte(state->substitute_cmd);
	TSPoint command_point = ts_node_start_point(state->substitute_cmd);
	e->new_text = new_text;
	e->old_text = ts_node_sub_parent_string(state->substitute_cmd, arg, state->input);
	e->start = ts_node_start_byte(arg) - command_start;
	e->end = ts_node_end_byte(arg) - command_start;
	e->start_point = ts_node_start_point(arg);
	e->end_point = ts_node_end_point(arg);
	if (e->start_point.row == command_point.row) {
		e->start_point.column -= command_point.column;
	}
	if (e->end_point.row == command_point.row) {
		e->end_point.column -= command_point.column;
	}
	e->start_point.row -= command_point.row;
	e->end_point.row -= command_point.row;
	return e;
}

static void replace_whitespaces(char *s, char ch) {
	while (*s) {
		if (*s == '#') {
			while (*s && *s != '\r' && *s != '\n') {
				*s = ch;
				s++;
			}
		}
		if (isspace(*s)) {
			*s = ch;
		}
		s++;
	}
}

void free_tsr2cmd_edit(struct tsr2cmd_edit *edit) {
	free(edit->new_text);
	free(edit->old_text);
	free(edit);
}

static char *do_handle_substitution_cmd(struct tsr2cmd_state *state, TSNode inn_cmd) {
	RzCore *core = state->core;
	int value = core->num->value;
	char *inn_str = ts_node_sub_parent_string(state->substitute_cmd, inn_cmd, state->input);

	// save current color and disable it
	int ocolor = rz_config_get_i(core->config, "scr.color");
	rz_config_set_i(core->config, "scr.color", 0);
	core->cmd_in_backticks = true;

	// execute the sub command
	char *o_out = inn_str[0] == '!' ? rz_core_cmd_str_pipe(core, inn_str) : rz_core_cmd_str(core, inn_str);

	// restore color and cmd_in_backticks
	core->num->value = value;
	core->cmd_in_backticks = false;
	rz_config_set_i(core->config, "scr.color", ocolor);
	free(inn_str);
	if (!o_out) {
		RZ_LOG_DEBUG("Something wrong with inner command\n");
		return NULL;
	}

	// replace the output of the sub command with the current argument
	char *out = rz_str_dup(o_out);
	rz_str_trim(out);
	RZ_LOG_DEBUG("output of inner command: '%s'\n", out);
	free(o_out);

	// replace newlines and similar with spaces
	replace_whitespaces(out, ' ');
	return out;
}

static bool handle_cmd_substitution_arg(struct tsr2cmd_state *state, TSNode arg, RzList /*<struct tsr2cmd_edit *>*/ *edits) {
	TSNode inn_cmd = ts_node_child(arg, 1);
	rz_return_val_if_fail(!ts_node_is_null(inn_cmd), false);
	char *out = do_handle_substitution_cmd(state, inn_cmd);
	if (!out) {
		return false;
	}

	char *res = NULL;
	// escape special chars to prevent creation of new tokens when parsing again
	if (is_ts_double_quoted_arg(ts_node_parent(arg))) {
		res = rz_cmd_escape_arg(out, RZ_CMD_ESCAPE_DOUBLE_QUOTED_ARG);
	} else if (is_ts_pf_arg(ts_node_parent(arg))) {
		res = rz_cmd_escape_arg(out, RZ_CMD_ESCAPE_PF_ARG);
	} else {
		res = rz_cmd_escape_arg(out, RZ_CMD_ESCAPE_MULTI_ARG);
	}
	free(out);
	if (!res) {
		return false;
	}
	struct tsr2cmd_edit *e = create_cmd_edit(state, arg, res);
	rz_list_append(edits, e);
	return true;
}

static bool is_group_of_args(TSNode args) {
	return is_ts_args(args) || is_ts_concatenation(args) ||
		is_ts_double_quoted_arg(args) ||
		is_ts_pf_concatenation(args) || is_ts_pf_args(args) ||
		is_ts_pf_dot_cmd_args(args) || is_ts_pf_new_args(args) ||
		is_ts_grep_specifier(args);
}

static bool is_arg(TSNode args) {
	return is_ts_arg(args) || is_ts_pf_arg(args);
}

static bool is_handled_args(TSNode args) {
	return is_group_of_args(args) || is_arg(args) ||
		is_ts_cmd_substitution_arg(args) || is_ts_grep_specifier(args);
}

static bool handle_substitution_args(struct tsr2cmd_state *state, TSNode args, RzList /*<struct tsr2cmd_edit *>*/ *edits) {
	if (is_group_of_args(args)) {
		uint32_t n_children = ts_node_named_child_count(args);
		uint32_t i;
		for (i = 0; i < n_children; i++) {
			TSNode arg = ts_node_named_child(args, i);
			bool res = handle_substitution_args(state, arg, edits);
			if (!res) {
				return false;
			}
		}
	} else if (is_ts_cmd_substitution_arg(args)) {
		return handle_cmd_substitution_arg(state, args, edits);
	} else if (is_arg(args)) {
		TSNode arg = ts_node_named_child(args, 0);
		return handle_substitution_args(state, arg, edits);
	}
	return true;
}

static char *do_handle_ts_unescape_arg(struct tsr2cmd_state *state, TSNode arg, bool raw, bool do_unwrap) {
	if (is_ts_arg(arg)) {
		return do_handle_ts_unescape_arg(state, ts_node_named_child(arg, 0), raw, do_unwrap);
	} else if (is_ts_arg_identifier(arg)) {
		char *arg_str = ts_node_sub_string(arg, state->input);
		if (raw) {
			return arg_str;
		}
		char *unescaped_arg = rz_cmd_unescape_arg(arg_str, RZ_CMD_ESCAPE_ONE_ARG);
		free(arg_str);
		return unescaped_arg;
	} else if (is_ts_single_quoted_arg(arg) || is_ts_double_quoted_arg(arg)) {
		char *o_arg_str = ts_node_sub_string(arg, state->input);
		if (raw) {
			return o_arg_str;
		}
		char *arg_str = o_arg_str;
		if (do_unwrap) {
			// remove quotes
			arg_str[strlen(arg_str) - 1] = '\0';
			arg_str++;
		}
		char *res;
		if (is_ts_single_quoted_arg(arg)) {
			res = rz_cmd_unescape_arg(arg_str, RZ_CMD_ESCAPE_SINGLE_QUOTED_ARG);
		} else {
			res = rz_cmd_unescape_arg(arg_str, RZ_CMD_ESCAPE_DOUBLE_QUOTED_ARG);
		}
		free(o_arg_str);
		return res;
	} else if (is_ts_concatenation(arg)) {
		uint32_t i, n_children = ts_node_named_child_count(arg);
		RzStrBuf *sb = rz_strbuf_new(NULL);
		for (i = 0; i < n_children; i++) {
			TSNode sub_arg = ts_node_named_child(arg, i);
			char *s = do_handle_ts_unescape_arg(state, sub_arg, raw, do_unwrap);
			rz_strbuf_append(sb, s);
			free(s);
		}
		return rz_strbuf_drain(sb);
	} else {
		return ts_node_sub_string(arg, state->input);
	}
}

static bool is_arg_raw(const RzCmdDesc *cd, size_t i) {
	if (!cd) {
		return false;
	}
	const RzCmdDescArg *arg = rz_cmd_desc_get_arg(cd, i);
	return arg && arg->type == RZ_CMD_ARG_TYPE_RAW;
}

static RzCmdParsedArgs *parse_args(struct tsr2cmd_state *state, TSNode args, bool do_unwrap, const RzCmdDesc *cd) {
	if (ts_node_is_null(args)) {
		return rz_cmd_parsed_args_newargs(0, NULL);
	} else if (is_ts_args(args)) {
		uint32_t n_children = ts_node_named_child_count(args);
		uint32_t i;
		char **unescaped_args = RZ_NEWS0(char *, n_children);
		for (i = 0; i < n_children; i++) {
			TSNode arg = ts_node_named_child(args, i);
			bool arg_raw = is_arg_raw(cd, i);
			bool do_unwrap_arg = do_unwrap;
			if (!do_unwrap && cd && !arg_raw) {
				do_unwrap_arg = true;
			}
			unescaped_args[i] = do_handle_ts_unescape_arg(state, arg, arg_raw, do_unwrap_arg);
		}
		RzCmdParsedArgs *res = rz_cmd_parsed_args_newargs(n_children, unescaped_args);
		for (i = 0; i < n_children; i++) {
			free(unescaped_args[i]);
		}
		free(unescaped_args);
		return res;
	} else {
		bool arg_raw = is_arg_raw(cd, 0);
		if (!do_unwrap && cd && !arg_raw) {
			do_unwrap = true;
		}
		char *unescaped_args[] = { do_handle_ts_unescape_arg(state, args, arg_raw, do_unwrap) };
		RzCmdParsedArgs *res = rz_cmd_parsed_args_newargs(1, unescaped_args);
		free(unescaped_args[0]);
		return res;
	}
}

static TSTree *apply_edits(struct tsr2cmd_state *state, RzList /*<struct tsr2cmd_edit *>*/ *edits) {
	struct tsr2cmd_edit *edit;
	RzListIter *it;

	RZ_LOG_DEBUG("old input = '%s'\n", state->input);
	rz_list_foreach (edits, it, edit) {
		RZ_LOG_DEBUG("apply_edits: about to replace '%s' with '%s'\n", edit->old_text, edit->new_text);
		state->input = rz_str_replace(state->input, edit->old_text, edit->new_text, 0);
	}
	RZ_LOG_DEBUG("new input = '%s'\n", state->input);
	return ts_parser_parse_string(state->parser, NULL, state->input, strlen(state->input));
}

static void substitute_args_fini(struct tsr2cmd_state *state) {
	TSTree *saved_tree = rz_pvector_pop(&state->saved_tree);
	if (state->tree != saved_tree) {
		ts_tree_delete(state->tree);
	}
	state->tree = saved_tree;

	char *saved_input = rz_pvector_pop(&state->saved_input);
	if (state->input != saved_input) {
		free(state->input);
	}
	state->input = saved_input;
}

static void substitute_args_init(struct tsr2cmd_state *state, TSNode command) {
	rz_pvector_push(&state->saved_input, state->input);
	rz_pvector_push(&state->saved_tree, state->tree);

	state->substitute_cmd = command;
	state->input = ts_node_sub_string(state->substitute_cmd, state->input);
	RZ_LOG_DEBUG("Shrinking input to '%s'\n", state->input);
}

static bool substitute_args_do(struct tsr2cmd_state *state, RzList /*<struct tsr2cmd_edit *>*/ *edits, TSNode *new_command) {
	TSTree *new_tree = apply_edits(state, edits);
	if (!new_tree) {
		return false;
	}

	TSNode root = ts_tree_root_node(new_tree);
	if (ts_node_has_error(root)) {
		ts_tree_delete(new_tree);
		return false;
	}
	*new_command = ts_node_named_child(root, 0);
	state->tree = new_tree;
	return true;
}

static bool substitute_args(struct tsr2cmd_state *state, TSNode args, TSNode *new_command) {
	RzList *edits = rz_list_newf((RzListFree)free_tsr2cmd_edit);
	bool res = false;

	if (is_handled_args(args)) {
		if (!handle_substitution_args(state, args, edits)) {
			goto err;
		}
	}

	res = substitute_args_do(state, edits, new_command);
err:
	rz_list_free(edits);
	return res;
}

// If do_unwrap is true, then quote unwrapping is always done, else cd is
// checked. An arg of raw type (this can be determined if cd is available)
// prevents unescaping and quote unwrapping regardless.
static RzCmdParsedArgs *ts_node_handle_arg_prargs(struct tsr2cmd_state *state, TSNode command, TSNode arg,
	uint32_t child_idx, bool do_unwrap, const RzCmdDesc *cd, bool grandparent) {
	RzCmdParsedArgs *res = NULL;
	TSNode new_command;
	substitute_args_init(state, command);
	bool ok = substitute_args(state, arg, &new_command);
	if (!ok) {
		RZ_LOG_ERROR("Error while substituting arguments\n");
		goto err;
	}

	arg = ts_node_named_child(new_command, child_idx);
	if (grandparent) {
		arg = ts_node_named_child(arg, 0);
	}
	res = parse_args(state, arg, do_unwrap, cd);
	if (res == NULL) {
		RZ_LOG_ERROR("Cannot parse arg\n");
		goto err;
	}
err:
	substitute_args_fini(state);
	return res;
}

static char *ts_node_handle_arg(struct tsr2cmd_state *state, TSNode command, TSNode arg, uint32_t child_idx,
	bool grandparent) {
	RzCmdParsedArgs *a = ts_node_handle_arg_prargs(state, command, arg, child_idx, true, NULL, grandparent);
	if (!a) {
		return NULL;
	}
	char *str = rz_cmd_parsed_args_argstr(a);
	rz_cmd_parsed_args_free(a);
	return str;
}

static void get_help_wrong_cmd(RzCore *core, const char *cmdname) {
	char *cmdname_help = rz_str_newf("%s?", cmdname);
	if (!cmdname_help) {
		return;
	}
	RzCmdParsedArgs *help_pra = rz_cmd_parsed_args_newcmd(cmdname_help);
	if (!help_pra) {
		goto cmdname_err;
	}
	bool use_color = core->print->flags & RZ_PRINT_FLAGS_COLOR;
	char *help_msg = rz_cmd_get_help(core->rcmd, help_pra, use_color, 0);
	if (!help_msg) {
		goto help_pra_err;
	}
	RZ_LOG_ERROR("%s", help_msg);
	free(help_msg);
help_pra_err:
	rz_cmd_parsed_args_free(help_pra);
cmdname_err:
	free(cmdname_help);
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(arged_stmt) {
	TSNode command = ts_node_child_by_field_name(node, "command", strlen("command"));
	rz_return_val_if_fail(!ts_node_is_null(command), false);

	TSNode command_extra = ts_node_child_by_field_name(command, "extra", strlen("extra"));
	char *command_str = NULL, *command_extra_str = NULL;
	if (!ts_node_is_null(command_extra)) {
		command_extra_str = ts_node_sub_string(command_extra, state->input);
		RZ_LOG_DEBUG("command_extra_str = '%s'\n", command_extra_str);

		ut32 start = ts_node_start_byte(command);
		ut32 end = ts_node_start_byte(command_extra);
		command_str = rz_str_newf("%.*s", end - start, state->input + start);
	} else {
		command_str = ts_node_sub_string(command, state->input);
	}

	rz_str_unescape(command_str);
	RZ_LOG_DEBUG("arged_stmt command: '%s'\n", command_str);
	TSNode args = ts_node_child_by_field_name(node, "args", strlen("args"));
	RzCmdStatus res = RZ_CMD_STATUS_INVALID;

	// FIXME: this special handling should be removed once we have a proper
	//        command tree
	if (!strcmp(command_str, "|.")) {
		char *cmd_str = ts_node_sub_string(args, state->input);
		char *exec_string = rz_str_newf(".%s", cmd_str);
		free(cmd_str);
		free(command_str);
		res = core_cmd_tsrzcmd(state->core, exec_string, state->split_lines, false);
		free(exec_string);
		free(command_extra_str);
		return res;
	}

	RzCmdParsedArgs *pr_args = NULL;
	if (!ts_node_is_null(args)) {
		RzCmdDesc *cd = rz_cmd_get_desc(state->core->rcmd, command_str);
		pr_args = ts_node_handle_arg_prargs(state, node, args, 1, false, cd, false);
		if (!pr_args) {
			goto err;
		}
		rz_cmd_parsed_args_setcmd(pr_args, command_str);
	} else {
		pr_args = rz_cmd_parsed_args_newcmd(command_str);
		if (!pr_args) {
			goto err;
		}
	}

	pr_args->extra = command_extra_str;
	pr_args->has_space_after_cmd = !ts_node_is_null(args) && ts_node_end_byte(command) < ts_node_start_byte(args);
	res = rz_cmd_call_parsed_args(state->core->rcmd, pr_args);
	if (res == RZ_CMD_STATUS_WRONG_ARGS) {
		const char *cmdname = rz_cmd_parsed_args_cmd(pr_args);
		RZ_LOG_ERROR("Wrong number of arguments passed to `%s`, see its help with `%s?`\n\n", cmdname, cmdname);
		get_help_wrong_cmd(state->core, cmdname);
	} else if (res == RZ_CMD_STATUS_NONEXISTINGCMD) {
		const char *cmdname = rz_cmd_parsed_args_cmd(pr_args);
		RZ_LOG_ERROR("Command '%s' does not exist.\n", cmdname);
		if (rz_str_endswith(cmdname, "?") && pr_args->argc > 1) {
			RZ_LOG_ERROR("Did you want to see the help? Try `%s` without any argument.\n", cmdname);
		} else {
			// Let's try to find the first command/group in the ancestor chain
			// that could provide some help
			RzCmdDesc *hcd = rz_cmd_get_desc_best(state->core->rcmd, cmdname);
			if (hcd) {
				RZ_LOG_ERROR("Displaying the help of command '%s'.\n\n", hcd->name);
				get_help_wrong_cmd(state->core, hcd->name);
			}
		}
	} else if (res == RZ_CMD_STATUS_ERROR) {
		RZ_LOG_DEBUG("Something wrong during the execution of `%s` command.\n", rz_cmd_parsed_args_cmd(pr_args));
	}

err:
	RZ_LOG_DEBUG("arged_stmt finished command: '%s'\n", command_str);
	rz_cmd_parsed_args_free(pr_args);
	free(command_extra_str);
	free(command_str);
	return res;
}

static bool is_push_stmt(TSNode node, const char *in) {
	if (ts_node_is_null(node) || strcmp(ts_node_type(node), "arged_stmt")) {
		return false;
	}
	TSNode cmd = ts_node_child_by_field_name(node, "command", strlen("command"));
	if (ts_node_is_null(cmd)) {
		return false;
	}
	TSNode extra = ts_node_child_by_field_name(cmd, "extra", strlen("extra"));
	char *s = NULL;
	if (!ts_node_is_null(extra)) {
		ut32 start = ts_node_start_byte(cmd);
		ut32 end = ts_node_start_byte(extra);
		s = rz_str_newf("%.*s", end - start, in + start);
	} else {
		s = ts_node_sub_string(cmd, in);
	}
	if (!s) {
		return false;
	}
	rz_str_unescape(s);
	bool ret = !strcmp(s, "<");
	free(s);
	return ret;
}

static bool has_push(TSNode node, const char *in) {
	if (is_push_stmt(node, in)) {
		return true;
	}
	uint32_t n = ts_node_named_child_count(node);
	for (uint32_t i = 0; i < n; i++) {
		if (has_push(ts_node_named_child(node, i), in)) {
			return true;
		}
	}
	return false;
}

RZ_IPI bool rz_core_cmd_has_push(const char *cstr) {
	if (RZ_STR_ISEMPTY(cstr)) {
		return false;
	}
	TSParser *parser = ts_parser_new();
	if (!parser) {
		return false;
	}
	bool ok = ts_parser_set_language(parser, tree_sitter_rzcmd());
	if (!ok) {
		ts_parser_delete(parser);
		return false;
	}
	TSTree *tree = ts_parser_parse_string(parser, NULL, cstr, strlen(cstr));
	if (!tree) {
		ts_parser_delete(parser);
		return false;
	}
	TSNode root = ts_tree_root_node(tree);
	bool ret = !ts_node_has_error(root) && has_push(root, cstr);
	ts_tree_delete(tree);
	ts_parser_delete(parser);
	return ret;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(macro_stmt) {
	RzCmdStatus res = RZ_CMD_STATUS_ERROR;
	char *macro_name_str = NULL;
	char **args_str = NULL, **argv_str = NULL;
	char *body_str = NULL;
	ut32 nargs = 0;

	TSNode macro_content = ts_node_named_child(node, 1);
	rz_return_val_if_fail(!ts_node_is_null(macro_content), false);

	TSNode macro_name = ts_node_child_by_field_name(macro_content, "name", strlen("name"));
	rz_return_val_if_fail(!ts_node_is_null(macro_name), false);
	macro_name_str = ts_node_sub_string(macro_name, state->input);
	if (!macro_name_str) {
		goto err;
	}

	TSNode args = ts_node_child_by_field_name(macro_content, "args", strlen("args"));
	if (!ts_node_is_null(args)) {
		nargs = ts_node_named_child_count(args);
	}
	args_str = RZ_NEWS0(char *, nargs + 1);
	if (!args_str) {
		goto err;
	}
	for (ut32 i = 0; i < nargs; i++) {
		TSNode arg = ts_node_named_child(args, i);
		args_str[i] = ts_node_sub_string(arg, state->input);
		if (!args_str[i]) {
			goto err;
		}
	}

	TSNode body = ts_node_child_by_field_name(macro_content, "body", strlen("body"));
	rz_return_val_if_fail(!ts_node_is_null(body), false);
	body_str = ts_node_sub_string(body, state->input);

	TSNode macro_call = ts_node_named_child(node, 2);
	if (!ts_node_is_null(macro_call)) {
		TSNode argv = ts_node_child_by_field_name(macro_call, "argv", strlen("argv"));
		ut32 nargv = ts_node_is_null(argv) ? 0 : ts_node_named_child_count(argv);
		argv_str = RZ_NEWS0(char *, nargv + 1);
		if (!argv_str) {
			goto err;
		}
		for (ut32 i = 0; i < nargv; i++) {
			TSNode arg = ts_node_named_child(argv, i);
			argv_str[i] = ts_node_sub_string(arg, state->input);
			if (!argv_str[i]) {
				goto err;
			}
		}
	}

	res = rz_macros_handler(state->core, macro_name_str, (const char **)args_str, body_str, (const char **)argv_str);
err:
	rz_str_argv_free(argv_str);
	free(body_str);
	rz_str_argv_free(args_str);
	free(macro_name_str);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(repeat_stmt) {
	TSNode number = ts_node_child_by_field_name(node, "arg", strlen("arg"));
	char *number_str = ts_node_sub_string(number, state->input);
	int rep = atoi(number_str);
	free(number_str);

	TSNode command = ts_node_child_by_field_name(node, "command", strlen("command"));
	if (rep > INTERACTIVE_MAX_REP && rz_cons_is_interactive()) {
		if (!rz_cons_yesno('n', "Are you sure to repeat this %d times? (y/N)", rep)) {
			return RZ_CMD_STATUS_INVALID;
		}
	}

	RzCmdStatus res = RZ_CMD_STATUS_OK;
	size_t i;
	for (i = 0; i < rep; i++) {
		RzCmdStatus cmd_res = handle_ts_stmt(state, command);
		UPDATE_CMD_STATUS_RES(res, cmd_res, err);
	}
err:
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(redirect_stmt) {
	bool pipecolor = rz_config_get_b(state->core->config, "scr.color.pipe");
	int ocolor = rz_config_get_i(state->core->config, "scr.color");
	int scr_html = -1;
	RzCmdStatus res = RZ_CMD_STATUS_INVALID, is_append = false, is_html = false;
	int fdn = 1;

	TSNode redirect_op = ts_node_child_by_field_name(node, "redirect_operator", strlen("redirect_operator"));
	if (is_ts_fdn_redirect_operator(redirect_op)) {
		// this is the default operation, no html and no append
	} else if (is_ts_fdn_append_operator(redirect_op)) {
		is_append = true;
	} else if (is_ts_html_redirect_operator(redirect_op)) {
		is_html = true;
	} else if (is_ts_html_append_operator(redirect_op)) {
		is_html = true;
		is_append = true;
	} else {
		RZ_LOG_ERROR("This should never happen, redirect_operator is no known type\n");
		rz_warn_if_reached();
	}

	if (is_html) {
		scr_html = rz_config_get_i(state->core->config, "scr.html");
		rz_config_set_i(state->core->config, "scr.html", true);
		pipecolor = true;
	} else {
		TSNode fd_desc = ts_node_named_child(redirect_op, 0);
		if (!ts_node_is_null(fd_desc)) {
			char *fd_str = ts_node_sub_string(fd_desc, state->input);
			fdn = atoi(fd_str);
			free(fd_str);
		}
	}

	rz_cons_set_interactive(false);
	// TODO: allow to use editor as the old behaviour

	// extract the string of the filename we need to write to
	TSNode arg = ts_node_child_by_field_name(node, "arg", strlen("arg"));
	char *arg_str = ts_node_handle_arg(state, node, arg, 2, false);
	if (!arg_str) {
		res = RZ_CMD_STATUS_INVALID;
		goto fail;
	}

	if (arg_str[0] == '$') {
		// redirect output of command to an alias variable
		RZ_LOG_DEBUG("redirect_stmt: alias = '%s'\n", arg_str);
		TSNode command = ts_node_child_by_field_name(node, "command", strlen("command"));
		char *command_str = ts_node_sub_string(command, state->input);

		char *output = rz_core_cmd_str(state->core, command_str);
		char *old_alias_value = rz_cmd_alias_get(state->core->rcmd, arg_str, 1);
		char *new_alias_value;
		const char *start_char = "$";
		if (is_append && old_alias_value) {
			start_char = "";
		} else {
			old_alias_value = "";
		}
		new_alias_value = rz_str_newf("%s%s%s", start_char, old_alias_value, output ? output : "");
		free(output);
		rz_cmd_alias_set(state->core->rcmd, arg_str, new_alias_value, 1);
		free(new_alias_value);
		free(command_str);
		res = RZ_CMD_STATUS_OK;
	} else {
		rz_cons_flush();
		RZ_LOG_DEBUG("redirect_stmt: fdn = %d, is_append = %d\n", fdn, is_append);
		rz_cons_push();
		rz_cons_set_flush(true);
		RzConsPipe *cpipe = rz_cons_pipe_open(arg_str, fdn, is_append);
		if (cpipe) {
			if (!pipecolor) {
				rz_config_set_i(state->core->config, "scr.color", COLOR_MODE_DISABLED);
			}
			TSNode command = ts_node_child_by_field_name(node, "command", strlen("command"));
			res = handle_ts_stmt(state, command);
			rz_cons_flush();
			rz_cons_pipe_close(cpipe);
		} else {
			RZ_LOG_WARN("Could not open pipe to %d\n", fdn);
		}
		rz_cons_pop();
	}
	free(arg_str);
fail:
	rz_cons_set_last_interactive();
	if (!pipecolor) {
		rz_config_set_i(state->core->config, "scr.color", ocolor);
	}
	if (scr_html != -1) {
		rz_config_set_i(state->core->config, "scr.html", scr_html);
	}
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(help_stmt) {
	size_t node_str_len = strlen(node_string);
	if (node_str_len >= 2 && !strcmp(node_string + node_str_len - 2, "?*")) {
		node_string[node_str_len - 2] = 0;
		const char *argv[2] = { NULL, node_string };
		int argc = node_str_len > 2 ? 2 : 1;
		return rz_cmd_help_search_handler(state->core, argc, argv, RZ_OUTPUT_MODE_STANDARD);
	} else if (node_str_len >= 3 && !strcmp(node_string + node_str_len - 3, "?*j")) {
		node_string[node_str_len - 3] = 0;
		const char *argv[2] = { NULL, node_string };
		int argc = node_str_len > 2 ? 2 : 1;
		return rz_cmd_help_search_handler(state->core, argc, argv, RZ_OUTPUT_MODE_JSON);
	} else if (node_str_len >= 3 && RZ_STR_EQ(node_string + node_str_len - 3, "?**")) {
		node_string[3] = 0;
		const char *argv[1] = { node_string };
		int argc = 1;
		return rz_cmd_help_search_interactive_handler(state->core, argc, argv);
	} else if (node_str_len >= 4 && RZ_STR_EQ(node_string + node_str_len - 4, "?**e")) {
		node_string[4] = 0;
		const char *argv[1] = { node_string };
		int argc = 1;
		return rz_cmd_help_search_interactive_settings_handler(state->core, argc, argv);
	} else if (node_str_len >= 4 && RZ_STR_EQ(node_string + node_str_len - 4, "?***")) {
		node_string[4] = 0;
		const char *argv[1] = { node_string };
		int argc = 1;
		return rz_cmd_help_search_interactive_everything_handler(state->core, argc, argv);
	}

	TSNode command = ts_node_child_by_field_name(node, "command", strlen("command"));
	char *command_str = ts_node_sub_string(command, state->input);
	TSNode args = ts_node_child_by_field_name(node, "args", strlen("args"));
	RzCmdParsedArgs *pr_args = NULL;
	RzCmdStatus res = RZ_CMD_STATUS_INVALID;
	if (!ts_node_is_null(args)) {
		RzCmdDesc *cd = rz_cmd_get_desc(state->core->rcmd, command_str);
		pr_args = ts_node_handle_arg_prargs(state, node, args, 1, false, cd, false);
		if (!pr_args) {
			goto err_else;
		}
		rz_cmd_parsed_args_setcmd(pr_args, command_str);
	} else {
		pr_args = rz_cmd_parsed_args_newcmd(command_str);
		if (!pr_args) {
			goto err_else;
		}
	}

	// let's try first with the new auto-generated help, if
	// something fails fallback to old behaviour
	bool use_color = state->core->print->flags & RZ_PRINT_FLAGS_COLOR;
	char *help_msg = rz_cmd_get_help(state->core->rcmd, pr_args, use_color, 0);
	if (help_msg) {
		rz_cons_printf("%s", help_msg);
		free(help_msg);
		res = RZ_CMD_STATUS_OK;
	}
err_else:
	rz_cmd_parsed_args_free(pr_args);
	free(command_str);
	return res;
}

static TSNode tmp_get_next_node(TSNode cur) {
	TSNode next = ts_node_next_named_sibling(cur);
	if (ts_node_is_null(next)) {
		next = ts_node_named_child(ts_node_parent(cur), 0);
	}
	return next;
}

static uint32_t tmp_get_idx(TSNode node) {
	uint32_t cnt = 0;
	while (!ts_node_is_null(node)) {
		node = ts_node_prev_named_sibling(node);
		cnt++;
	}
	return cnt - 1;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(tmp_stmt) {
	TSNode first_tmp = ts_node_named_child(node, 1);
	return handle_ts_stmt(state, first_tmp);
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(tmp_seek_op) {
	TSNode offset = ts_node_named_child(node, 0);
	char *offset_string = ts_node_handle_arg(state, ts_node_parent(node), offset, tmp_get_idx(node), true);
	if (!offset_string) {
		return RZ_CMD_STATUS_INVALID;
	}
	ut64 offset_val = rz_num_math(state->core->num, offset_string);
	ut64 orig_offset = state->core->offset;
	if (!offset_val && isalpha((int)offset_string[0])) {
		if (!rz_flag_get(state->core->flags, offset_string)) {
			RZ_LOG_ERROR("core: Invalid address (%s)\n", offset_string);
			free(offset_string);
			return RZ_CMD_STATUS_INVALID;
		}
	}
	if (offset_string[0] == '-' || offset_string[0] == '+') {
		offset_val += state->core->offset;
	}
	RZ_LOG_DEBUG("tmp_seek_op, changing offset to %" PFMT64x "\n", offset_val);
	rz_core_seek(state->core, offset_val, true);
	TSNode next = tmp_get_next_node(node);
	RzCmdStatus res = handle_ts_stmt_tmpseek(state, next);
	rz_core_seek(state->core, orig_offset, true);
	free(offset_string);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(tmp_blksz_op) {
	TSNode blksz = ts_node_named_child(node, 0);
	char *blksz_string = ts_node_handle_arg(state, ts_node_parent(node), blksz, tmp_get_idx(node), true);
	if (!blksz_string) {
		return RZ_CMD_STATUS_INVALID;
	}
	ut64 orig_blksz = state->core->blocksize;
	RZ_LOG_DEBUG("tmp_blksz_op, changing blksz to %s\n", blksz_string);
	rz_core_block_size(state->core, rz_num_math(state->core->num, blksz_string));
	TSNode next = tmp_get_next_node(node);
	RzCmdStatus res = handle_ts_stmt(state, next);
	rz_core_block_size(state->core, orig_blksz);
	free(blksz_string);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(tmp_fromto_op) {
	RzCore *core = state->core;
	TSNode fromto = ts_node_named_child(node, 0);
	RzCmdParsedArgs *a = ts_node_handle_arg_prargs(state, ts_node_parent(node), fromto, tmp_get_idx(node),
		true, NULL, true);
	if (!a || a->argc != 2 + 1) {
		rz_cmd_parsed_args_free(a);
		return RZ_CMD_STATUS_INVALID;
	}

	char *from_str = a->argv[1];
	char *to_str = a->argv[2];

	const char *fromvars[] = { "analysis.from", "diff.from", "graph.from",
		"search.from", "zoom.from", NULL };
	const char *tovars[] = { "analysis.to", "diff.to", "graph.to",
		"search.to", "zoom.to", NULL };
	ut64 from_val = rz_num_math(core->num, from_str);
	ut64 to_val = rz_num_math(core->num, to_str);
	RZ_LOG_DEBUG("tmp_fromto_op, changing fromto to (%" PFMT64x ", %" PFMT64x ")\n", from_val, to_val);

	RzConfigHold *hc = rz_config_hold_new(core->config);
	int i;
	for (i = 0; fromvars[i]; i++) {
		rz_config_hold_var(hc, fromvars[i], NULL);
		rz_config_set_i(core->config, fromvars[i], from_val);
	}
	for (i = 0; tovars[i]; i++) {
		rz_config_hold_var(hc, tovars[i], NULL);
		rz_config_set_i(core->config, tovars[i], to_val);
	}

	TSNode next = tmp_get_next_node(node);
	RzCmdStatus res = handle_ts_stmt(state, next);

	rz_config_hold_restore(hc);

	rz_config_hold_free(hc);
	rz_cmd_parsed_args_free(a);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(tmp_arch_op) {
	RzCore *core = state->core;
	TSNode arg = ts_node_named_child(node, 0);
	char *arg_str = ts_node_handle_arg(state, ts_node_parent(node), arg, tmp_get_idx(node), true);
	if (!arg_str) {
		return RZ_CMD_STATUS_INVALID;
	}
	char *tmparch, *tmpbits;
	bool is_arch_set = false, is_bits_set = false;
	bool oldfixedarch = core->fixedarch, oldfixedbits = core->fixedbits;
	int cmd_ignbithints = -1;
	int bits = -1;

	// change arch and bits
	char *q = strchr(arg_str, ':');
	if (q) {
		*q++ = '\0';
		bits = rz_num_math(core->num, q);
	}
	is_arch_set = set_tmp_arch(core, arg_str, &tmparch);

	if (bits > 0) {
		is_bits_set = set_tmp_bits(core, bits, &tmpbits, &cmd_ignbithints);
	}

	// execute command or next tmp op with changed settings
	TSNode next = tmp_get_next_node(node);
	RzCmdStatus res = handle_ts_stmt(state, next);

	// restore original settings
	if (is_arch_set) {
		core->fixedarch = oldfixedarch;
		rz_config_set(core->config, "asm.arch", tmparch);
		free(tmparch);
	}
	if (is_bits_set) {
		rz_config_set(core->config, "asm.bits", tmpbits);
		core->fixedbits = oldfixedbits;
		free(tmpbits);
	}
	if (cmd_ignbithints != -1) {
		rz_config_set_i(core->config, "analysis.ignbithints", cmd_ignbithints);
	}
	free(arg_str);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(tmp_bits_op) {
	RzCore *core = state->core;
	TSNode arg = ts_node_named_child(node, 0);
	char *arg_str = ts_node_handle_arg(state, ts_node_parent(node), arg, tmp_get_idx(node), true);
	if (!arg_str) {
		return RZ_CMD_STATUS_INVALID;
	}
	bool oldfixedbits = core->fixedbits;
	char *tmpbits;
	int cmd_ignbithints;

	int bits = rz_num_math(core->num, arg_str);
	set_tmp_bits(core, bits, &tmpbits, &cmd_ignbithints);

	TSNode next = tmp_get_next_node(node);
	RzCmdStatus res = handle_ts_stmt(state, next);

	rz_config_set(core->config, "asm.bits", tmpbits);
	core->fixedbits = oldfixedbits;
	rz_config_set_i(core->config, "analysis.ignbithints", cmd_ignbithints);

	free(tmpbits);
	free(arg_str);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(tmp_nthi_op) {
	RzCore *core = state->core;
	TSNode arg = ts_node_named_child(node, 0);
	char *arg_str = ts_node_handle_arg(state, ts_node_parent(node), arg, tmp_get_idx(node), true);
	if (!arg_str) {
		return RZ_CMD_STATUS_INVALID;
	}

	ut64 orig_offset = state->core->offset;
	int index = rz_num_math(core->num, arg_str);
	if (rz_core_seek_bb_instruction(core, index)) {
		core->tmpseek = true;
	}

	TSNode next = tmp_get_next_node(node);
	RzCmdStatus res = handle_ts_stmt_tmpseek(state, next);

	rz_core_seek(core, orig_offset, true);

	free(arg_str);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(tmp_eval_op) {
	// TODO: support cmd_substitution in tmp_eval_args
	RzCore *core = state->core;
	TSNode args = ts_node_named_child(node, 0);

	RzConfigHold *hc = rz_config_hold_new(core->config);
	uint32_t i, n_args = ts_node_named_child_count(args);
	for (i = 0; i < n_args; i++) {
		TSNode arg = ts_node_named_child(args, i);
		char *arg_str = ts_node_sub_string(arg, state->input);
		char *eq = strchr(arg_str, '=');
		if (eq) {
			*eq = 0;
			rz_config_hold_var(hc, arg_str, NULL);
			rz_config_set_any(core->config, arg_str, eq + 1);
		} else {
			RZ_LOG_ERROR("core: Missing '=' in e: expression (%s)\n", arg_str);
		}
		free(arg_str);
	}

	TSNode next = tmp_get_next_node(node);
	RzCmdStatus res = handle_ts_stmt(state, next);

	rz_config_hold_restore(hc);
	rz_config_hold_free(hc);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(tmp_fs_op) {
	RzCore *core = state->core;
	TSNode arg = ts_node_named_child(node, 0);
	char *arg_str = ts_node_handle_arg(state, ts_node_parent(node), arg, tmp_get_idx(node), true);
	if (!arg_str) {
		return RZ_CMD_STATUS_INVALID;
	}
	rz_flag_space_push(core->flags, arg_str);
	TSNode next = tmp_get_next_node(node);
	RzCmdStatus res = handle_ts_stmt(state, next);
	rz_flag_space_pop(core->flags);
	free(arg_str);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(tmp_reli_op) {
	RzCore *core = state->core;
	TSNode arg = ts_node_named_child(node, 0);
	char *arg_str = ts_node_handle_arg(state, ts_node_parent(node), arg, tmp_get_idx(node), true);
	if (!arg_str) {
		return RZ_CMD_STATUS_INVALID;
	}
	ut64 orig_offset = state->core->offset;
	ut64 addr = rz_num_math(core->num, arg_str);
	if (addr) {
		rz_core_seek_opcode(core, addr, false);
	}
	TSNode next = tmp_get_next_node(node);
	RzCmdStatus res = handle_ts_stmt_tmpseek(state, next);
	rz_core_seek(state->core, orig_offset, true);
	free(arg_str);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(tmp_kuery_op) {
	RzCore *core = state->core;
	TSNode arg = ts_node_named_child(node, 0);
	char *arg_str = ts_node_handle_arg(state, ts_node_parent(node), arg, tmp_get_idx(node), true);
	if (!arg_str) {
		return RZ_CMD_STATUS_INVALID;
	}
	ut64 orig_offset = state->core->offset;
	char *out = sdb_querys(core->sdb, NULL, 0, arg_str);
	if (out) {
		rz_core_seek(core, rz_num_math(core->num, out), true);
		free(out);
	}
	TSNode next = tmp_get_next_node(node);
	RzCmdStatus res = handle_ts_stmt_tmpseek(state, next);
	rz_core_seek(state->core, orig_offset, true);
	free(arg_str);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(tmp_fd_op) {
	RzCore *core = state->core;
	TSNode arg = ts_node_named_child(node, 0);
	char *arg_str = ts_node_handle_arg(state, ts_node_parent(node), arg, tmp_get_idx(node), true);
	if (!arg_str) {
		return RZ_CMD_STATUS_INVALID;
	}
	int tmpfd = core->io->desc ? core->io->desc->fd : -1;
	rz_io_use_fd(core->io, atoi(arg_str));
	TSNode next = tmp_get_next_node(node);
	RzCmdStatus res = handle_ts_stmt(state, next);
	rz_io_use_fd(core->io, tmpfd);
	free(arg_str);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(tmp_reg_op) {
	RzCore *core = state->core;
	TSNode arg = ts_node_named_child(node, 0);
	char *arg_str = ts_node_handle_arg(state, ts_node_parent(node), arg, tmp_get_idx(node), true);
	if (!arg_str) {
		return RZ_CMD_STATUS_INVALID;
	}
	ut64 orig_offset = state->core->offset;
	// TODO: add support for operations (e.g. @r:PC+10)
	ut64 regval = rz_debug_reg_get(core->dbg, arg_str);
	rz_core_seek(core, regval, true);
	TSNode next = tmp_get_next_node(node);
	RzCmdStatus res = handle_ts_stmt_tmpseek(state, next);
	rz_core_seek(core, orig_offset, true);
	free(arg_str);
	return res;
}

static bool handle_tmp_desc(struct tsr2cmd_state *state, TSNode command, const ut8 *buf, int sz) {
	RzCore *core = state->core;
	int pamode = !core->io->va;
	RzCmdStatus res = RZ_CMD_STATUS_INVALID, o_fixedblock = core->fixedblock;
	RzBuffer *b = rz_buf_new_with_bytes(buf, sz);
	int cur_fd = rz_io_fd_get_current(core->io);
	RzIODesc *d = rz_io_open_buffer(core->io, b, RZ_PERM_RWX, 0);
	if (!d) {
		RZ_LOG_ERROR("core: Cannot open io buffer\n");
		goto out_buf;
	}
	if (pamode) {
		rz_config_set_i(core->config, "io.va", 1);
	}
	rz_io_map_new(core->io, d->fd, d->perm, 0, core->offset, rz_buf_size(b));
	ut32 obsz = core->blocksize;
	rz_core_block_size(core, rz_buf_size(b));
	core->fixedblock = true;
	rz_core_block_read(core);

	res = handle_ts_stmt(state, command);

	core->fixedblock = o_fixedblock;
	if (pamode) {
		rz_config_set_i(core->config, "io.va", 0);
	}
	rz_io_desc_close(d);
	rz_core_block_size(core, obsz);
	rz_io_use_fd(core->io, cur_fd);

out_buf:
	rz_buf_free(b);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(tmp_file_op) {
	TSNode arg = ts_node_named_child(node, 0);
	char *arg_str = ts_node_handle_arg(state, ts_node_parent(node), arg, tmp_get_idx(node), true);
	if (!arg_str) {
		return RZ_CMD_STATUS_INVALID;
	}
	size_t sz;
	RzCmdStatus res = RZ_CMD_STATUS_INVALID;

	char *f = rz_file_slurp(arg_str, &sz);
	if (!f) {
		RZ_LOG_ERROR("core: Cannot open '%s'\n", arg_str);
		goto out;
	}

	TSNode next = tmp_get_next_node(node);
	res = handle_tmp_desc(state, next, (ut8 *)f, (int)sz);

	free(f);
out:
	free(arg_str);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(tmp_string_op) {
	TSNode arg = ts_node_named_child(node, 0);
	char *arg_str = ts_node_handle_arg(state, ts_node_parent(node), arg, tmp_get_idx(node), true);
	if (!arg_str) {
		return RZ_CMD_STATUS_INVALID;
	}
	int sz;

	sz = strlen(arg_str);
	const ut8 *buf = (const ut8 *)arg_str;

	TSNode next = tmp_get_next_node(node);
	RzCmdStatus res = handle_tmp_desc(state, next, buf, sz);

	free(arg_str);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(tmp_value_op) {
	RzCore *core = state->core;
	TSNode arg = ts_node_named_child(node, 0);
	char *arg_str = ts_node_handle_arg(state, ts_node_parent(node), arg, tmp_get_idx(node), true);
	if (!arg_str) {
		return RZ_CMD_STATUS_INVALID;
	}

	ut64 v = rz_num_math(core->num, arg_str);
	ut8 buf[8] = { 0 };
	int be = rz_config_get_b(core->config, "cfg.bigendian");
	int bi = rz_config_get_i(core->config, "asm.bits");

	rz_write_ble(buf, v, be, bi);
	int sz = bi / 8;

	TSNode next = tmp_get_next_node(node);
	RzCmdStatus res = handle_tmp_desc(state, next, buf, sz);

	free(arg_str);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(tmp_hex_op) {
	TSNode arg = ts_node_named_child(node, 0);
	char *arg_str = ts_node_handle_arg(state, ts_node_parent(node), arg, tmp_get_idx(node), true);
	if (!arg_str) {
		return RZ_CMD_STATUS_INVALID;
	}
	int sz;
	RzCmdStatus res = RZ_CMD_STATUS_INVALID;

	size_t len = strlen(arg_str);
	ut8 *buf = RZ_NEWS(ut8, len + 1);
	sz = rz_hex_str2bin(arg_str, buf);
	if (!sz) {
		RZ_LOG_ERROR("core: Invalid hexpair for @x:\n");
		goto out;
	}

	TSNode next = tmp_get_next_node(node);
	res = handle_tmp_desc(state, next, buf, sz);

out:
	free(buf);
	free(arg_str);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_flags_stmt) {
	RzCore *core = state->core;
	TSNode command = ts_node_named_child(node, 0);
	TSNode arg = ts_node_named_child(node, 1);
	char *arg_str = NULL;
	if (!ts_node_is_null(arg)) {
		arg_str = ts_node_handle_arg(state, node, arg, 1, false);
		if (!arg_str) {
			return RZ_CMD_STATUS_INVALID;
		}
	}
	ut64 offorig = core->offset;
	const RzSpace *flagspace = rz_flag_space_cur(core->flags);
	RzFlagItem *flag;
	RzListIter *iter;
	RzCmdStatus ret = RZ_CMD_STATUS_OK;
	RzList *match_flag_items = rz_list_newf((RzListFree)rz_flag_item_free);
	if (!match_flag_items) {
		return RZ_CMD_STATUS_OK;
	}

	/* duplicate flags that match word, to be sure the command is going to
	   be executed on flags values at the moment the command is called
	   (without side effects) */
	struct duplicate_flag_t u = {
		.ret = match_flag_items,
		.word = arg_str,
	};
	rz_flag_foreach_space(core->flags, flagspace, duplicate_flag, &u);

	/* for all flags that match */
	rz_list_foreach (match_flag_items, iter, flag) {
		if (rz_cons_is_breaked()) {
			break;
		}

		RZ_LOG_DEBUG("iter_flags_stmt: seek to %" PFMT64x "\n", flag->offset);
		rz_core_seek(core, flag->offset, true);
		RzCmdStatus cmd_res = handle_ts_stmt_tmpseek(state, command);
		rz_core_task_yield(&core->tasks);
		UPDATE_CMD_STATUS_RES(ret, cmd_res, err);
	}

err:
	rz_list_free(match_flag_items);
	rz_core_seek(core, offorig, true);
	free(arg_str);
	return ret;
}

enum dbt_commands_mode {
	DBT_COMMANDS_MODE_ADDR,
	DBT_COMMANDS_MODE_BP,
	DBT_COMMANDS_MODE_SP,
};

static bool iter_dbt_commands(struct tsr2cmd_state *state, TSNode node, enum dbt_commands_mode mode) {
	RzCore *core = state->core;
	TSNode command = ts_node_named_child(node, 0);
	RzList *list = rz_debug_frames(core->dbg, UT64_MAX);
	ut64 orig_offset = core->offset;
	RzDebugFrame *frame;
	RzListIter *iter;
	RzCmdStatus res = RZ_CMD_STATUS_OK;

	rz_list_foreach (list, iter, frame) {
		switch (mode) {
		case DBT_COMMANDS_MODE_ADDR:
			rz_core_seek(core, frame->addr, true);
			break;
		case DBT_COMMANDS_MODE_SP:
			rz_core_seek(core, frame->sp, true);
			break;
		case DBT_COMMANDS_MODE_BP:
			rz_core_seek(core, frame->bp, true);
			break;
		default:
			rz_warn_if_reached();
			return RZ_CMD_STATUS_INVALID;
		}
		RzCmdStatus cmd_res = handle_ts_stmt_tmpseek(state, command);
		rz_cons_newline();
		UPDATE_CMD_STATUS_RES(res, cmd_res, err);
	}
err:
	rz_core_seek(core, orig_offset, true);
	rz_list_free(list);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_dbta_stmt) {
	return iter_dbt_commands(state, node, DBT_COMMANDS_MODE_ADDR);
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_dbtb_stmt) {
	return iter_dbt_commands(state, node, DBT_COMMANDS_MODE_BP);
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_dbts_stmt) {
	return iter_dbt_commands(state, node, DBT_COMMANDS_MODE_SP);
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_file_lines_stmt) {
	RzCore *core = state->core;
	RzCmdStatus res = RZ_CMD_STATUS_OK;
	TSNode command = ts_node_named_child(node, 0);
	TSNode arg = ts_node_named_child(node, 1);
	char *arg_str = ts_node_handle_arg(state, node, arg, 1, false);
	if (!arg_str) {
		return RZ_CMD_STATUS_INVALID;
	}
	ut64 orig_offset = core->offset;
	FILE *fd = rz_sys_fopen(arg_str, "r");
	if (!fd) {
		res = RZ_CMD_STATUS_INVALID;
		goto arg_out;
	}

	while (!feof(fd)) {
		char buf[1024];
		buf[0] = '\0';
		if (!fgets(buf, sizeof(buf), fd)) {
			break;
		}
		ut64 addr = rz_num_math(core->num, buf);
		rz_core_seek(core, addr, true);
		RzCmdStatus cmd_res = handle_ts_stmt_tmpseek(state, command);
		UPDATE_CMD_STATUS_RES(res, cmd_res, err);
	}
err:
	rz_core_seek(core, orig_offset, true);
	fclose(fd);

arg_out:
	free(arg_str);
	return res;
}

static RzCmdStatus do_iter_offsets(RzCore *core, struct tsr2cmd_state *state, TSNode *command, RzCmdParsedArgs *a, bool has_size) {
	RzCmdStatus res = RZ_CMD_STATUS_OK;

	const char *s;
	int i;
	ut64 orig_offset = core->offset;
	ut64 orig_blk_sz = core->blocksize;
	rz_cmd_parsed_args_foreach_arg(a, i, s) {
		ut64 addr = rz_num_math(core->num, s);
		ut64 blk_sz = core->blocksize;
		if (has_size) {
			blk_sz = rz_num_math(core->num, a->argv[i++ + 1]);
		}
		rz_core_seek(core, addr, true);
		if (has_size) {
			rz_core_block_size(core, blk_sz);
		}
		RzCmdStatus cmd_res = handle_ts_stmt_tmpseek(state, *command);
		rz_cons_flush();
		UPDATE_CMD_STATUS_RES(res, cmd_res, err);
	}

err:
	if (has_size) {
		rz_core_block_size(core, orig_blk_sz);
	}
	rz_core_seek(core, orig_offset, true);
	return res;
}

static RzCmdStatus iter_offsets_common(struct tsr2cmd_state *state, TSNode node, bool has_size) {
	RzCore *core = state->core;
	TSNode command = ts_node_named_child(node, 0);
	if (ts_node_named_child_count(node) < 2) {
		// no offsets provided, all's good.
		return RZ_CMD_STATUS_OK;
	}

	TSNode args = ts_node_named_child(node, 1);

	RzCmdParsedArgs *a = ts_node_handle_arg_prargs(state, node, args, 1, true, NULL, false);
	if (!a || (has_size && (a->argc - 1) % 2 != 0)) {
		RZ_LOG_ERROR("Cannot parse args\n");
		rz_cmd_parsed_args_free(a);
		return RZ_CMD_STATUS_INVALID;
	}

	RzCmdStatus res = do_iter_offsets(core, state, &command, a, has_size);
	rz_cmd_parsed_args_free(a);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_offsets_stmt) {
	return iter_offsets_common(state, node, false);
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_offsetssizes_stmt) {
	return iter_offsets_common(state, node, true);
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_instrs_stmt) {
	TSNode command = ts_node_named_child(node, 0);
	RzCore *core = state->core;
	RzCmdStatus res = RZ_CMD_STATUS_OK;
	ut64 orig_offset = core->offset;
	int bs = core->blocksize;
	RzList *bbl = rz_analysis_get_blocks_in(core->analysis, core->offset);
	if (!bbl || rz_list_empty(bbl)) {
		RZ_LOG_ERROR("core: No basic block contains current address\n");
		return RZ_CMD_STATUS_INVALID;
	}
	RzAnalysisBlock *bb = rz_list_last_val(bbl);
	rz_analysis_block_ref(bb);
	rz_list_free(bbl);

	int i;
	for (i = 0; i < bb->ninstr; i++) {
		ut64 i_addr = rz_analysis_block_get_op_addr(bb, i);
		int sz = rz_analysis_block_get_op_size(bb, i);
		rz_core_block_size(core, sz);
		rz_core_seek(core, i_addr, true);
		RzCmdStatus cmd_res = handle_ts_stmt_tmpseek(state, command);
		UPDATE_CMD_STATUS_RES(res, cmd_res, err);
		if (rz_cons_is_breaked()) {
			break;
		}
	}
	rz_analysis_block_unref(bb);

err:
	rz_core_block_size(core, bs);
	rz_core_seek(core, orig_offset, true);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_step_stmt) {
	TSNode command = ts_node_named_child(node, 0);
	TSNode args = ts_node_named_child(node, 1);
	RzCmdParsedArgs *a = ts_node_handle_arg_prargs(state, node, args, 1, true, NULL, false);
	if (!a || a->argc != 3 + 1) {
		rz_cmd_parsed_args_free(a);
		return RZ_CMD_STATUS_INVALID;
	}

	RzCore *core = state->core;
	RzCmdStatus res = RZ_CMD_STATUS_OK;
	ut64 orig_offset = core->offset;
	int bs = core->blocksize;

	char *from_str = a->argv[1];
	char *to_str = a->argv[2];
	char *step_str = a->argv[3];
	ut64 from = rz_num_math(core->num, from_str);
	ut64 to = rz_num_math(core->num, to_str);
	ut64 step = rz_num_math(core->num, step_str);

	ut64 cur;
	for (cur = from; cur < to; cur += step) {
		rz_core_seek(core, cur, true);
		rz_core_block_size(core, step);
		RzCmdStatus cmd_res = handle_ts_stmt_tmpseek(state, command);
		UPDATE_CMD_STATUS_RES(res, cmd_res, err);
		if (rz_cons_is_breaked()) {
			break;
		}
	}

err:
	rz_core_block_size(core, bs);
	rz_core_seek(core, orig_offset, true);
	rz_cmd_parsed_args_free(a);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_hit_stmt) {
	RzCore *core = state->core;
	TSNode command = ts_node_named_child(node, 0);
	TSNode search_cmd = ts_node_named_child(node, 1);
	char *command_str = ts_node_sub_string(command, state->input);
	char *cmdhit = rz_str_dup(rz_config_get(core->config, "cmd.hit"));
	rz_config_set(core->config, "cmd.hit", command_str);
	RzCmdStatus res = handle_ts_stmt(state, search_cmd);
	rz_config_set(core->config, "cmd.hit", cmdhit);
	free(command_str);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_bbs_stmt) {
	RzCore *core = state->core;
	TSNode command = ts_node_named_child(node, 0);
	RzAnalysisFunction *fcn = rz_analysis_get_fcn_in(core->analysis, core->offset, 0);
	ut64 offorig = core->offset;
	ut64 obs = core->blocksize;
	if (!fcn) {
		return RZ_CMD_STATUS_INVALID;
	}

	void **iter;
	RzAnalysisBlock *bb;
	RzCmdStatus ret = RZ_CMD_STATUS_OK;
	rz_pvector_sort(fcn->bbs, bb_cmp, NULL);
	rz_pvector_foreach (fcn->bbs, iter) {
		bb = (RzAnalysisBlock *)*iter;
		rz_core_seek(core, bb->addr, true);
		rz_core_block_size(core, bb->size);
		RzCmdStatus cmd_res = handle_ts_stmt_tmpseek(state, command);
		UPDATE_CMD_STATUS_RES(ret, cmd_res, err);
	}
err:
	rz_core_block_size(core, obs);
	rz_core_seek(core, offorig, true);
	return ret;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_interpret_stmt) {
	// convert @@c: command into a @@= one, by using the output of the
	// in_cmd as addr of @@=
	RzCmdStatus res = RZ_CMD_STATUS_INVALID;
	TSNode in_cmd = ts_node_named_child(node, 1);
	substitute_args_init(state, node);

	RzList *edits = rz_list_newf((RzListFree)free_tsr2cmd_edit);
	if (!edits) {
		goto edits_err;
	}

	char *in_cmd_out = do_handle_substitution_cmd(state, in_cmd);
	if (!in_cmd_out) {
		goto err;
	}

	char *in_cmd_out_es = rz_cmd_escape_arg(in_cmd_out, RZ_CMD_ESCAPE_MULTI_ARG);
	free(in_cmd_out);
	struct tsr2cmd_edit *e = create_cmd_edit(state, in_cmd, in_cmd_out_es);
	rz_list_append(edits, e);

	TSNode op = ts_node_child(node, 1);
	e = create_cmd_edit(state, op, rz_str_dup("@@="));
	rz_list_append(edits, e);

	TSNode new_command;
	if (!substitute_args_do(state, edits, &new_command)) {
		goto err;
	}
	res = handle_ts_stmt(state, new_command);
err:
	rz_list_free(edits);
edits_err:
	substitute_args_fini(state);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_interpret_offsetssizes_stmt) {
	// convert @@@c: command into a @@@= one, by using the output of the
	// in_cmd as addr/blksz of @@@=
	RzCmdStatus res = RZ_CMD_STATUS_INVALID;
	TSNode in_cmd = ts_node_named_child(node, 1);
	substitute_args_init(state, node);

	RzList *edits = rz_list_newf((RzListFree)free_tsr2cmd_edit);
	if (!edits) {
		goto edits_err;
	}

	char *in_cmd_out = do_handle_substitution_cmd(state, in_cmd);
	if (!in_cmd_out) {
		goto err;
	}

	char *in_cmd_out_es = rz_cmd_escape_arg(in_cmd_out, RZ_CMD_ESCAPE_MULTI_ARG);
	free(in_cmd_out);
	struct tsr2cmd_edit *e = create_cmd_edit(state, in_cmd, in_cmd_out_es);
	rz_list_append(edits, e);

	TSNode op = ts_node_child(node, 1);
	e = create_cmd_edit(state, op, rz_str_dup("@@@="));
	rz_list_append(edits, e);

	TSNode new_command;
	if (!substitute_args_do(state, edits, &new_command)) {
		goto err;
	}
	res = handle_ts_stmt(state, new_command);
err:
	rz_list_free(edits);
edits_err:
	substitute_args_fini(state);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_comment_stmt) {
	RzCore *core = state->core;
	TSNode command = ts_node_named_child(node, 0);
	TSNode filter_node = ts_node_named_child(node, 1);
	char *glob = !ts_node_is_null(filter_node)
		? ts_node_sub_string(filter_node, state->input)
		: NULL;
	ut64 off = core->offset;
	RzCmdStatus res = RZ_CMD_STATUS_OK;
	RzIntervalTreeIter it;
	RzAnalysisMetaItem *meta;
	RzIntervalTree *meta_tree = rz_analysis_get_meta(core->analysis);
	rz_interval_tree_foreach (meta_tree, it, meta) {
		if (meta->type != RZ_META_TYPE_COMMENT) {
			continue;
		}
		if (!glob || (meta->str && rz_str_glob(meta->str, glob))) {
			rz_core_seek(core, rz_interval_tree_iter_get(&it)->start, true);
			RzCmdStatus cmd_res = handle_ts_stmt_tmpseek(state, command);
			UPDATE_CMD_STATUS_RES(res, cmd_res, err);
		}
	}
err:
	rz_core_seek(core, off, false);
	free(glob);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_import_stmt) {
	RzCore *core = state->core;
	TSNode command = ts_node_named_child(node, 0);
	RzBinSymbol *imp;
	ut64 offorig = core->offset;
	RzBinObject *o = rz_bin_cur_object(core->bin);
	RzPVector *symbols = o ? (RzPVector *)rz_bin_object_get_symbols(o) : NULL;
	if (!symbols) {
		return RZ_CMD_STATUS_OK;
	}

	RzList *lost = rz_list_newf(free);
	void **it;
	rz_pvector_foreach (symbols, it) {
		imp = *it;
		if (!imp->is_imported) {
			continue;
		}
		ut64 *n = RZ_NEW(ut64);
		*n = imp->vaddr;
		rz_list_append(lost, n);
	}
	ut64 *naddr;
	RzListIter *iter;
	RzCmdStatus res = RZ_CMD_STATUS_OK;
	rz_list_foreach (lost, iter, naddr) {
		ut64 addr = *naddr;
		if (addr != UT64_MAX) {
			rz_core_seek(core, addr, true);
			RzCmdStatus cmd_res = handle_ts_stmt_tmpseek(state, command);
			UPDATE_CMD_STATUS_RES(res, cmd_res, err);
		}
	}
err:
	rz_core_seek(core, offorig, true);
	rz_list_free(lost);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_register_stmt) {
	RzCore *core = state->core;
	TSNode command = ts_node_named_child(node, 0);
	ut64 offorig = core->offset;
	RzReg *reg = rz_core_reg_default(core);
	int i;
	RzCmdStatus res = RZ_CMD_STATUS_OK;
	for (i = 0; i < RZ_REG_TYPE_LAST; i++) {
		RzRegItem *item;
		ut64 value;
		const RzList *head = rz_reg_get_list(reg, i);
		if (!head) {
			continue;
		}
		RzList *list = rz_list_newf(free);
		RzListIter *iter;
		rz_list_foreach (head, iter, item) {
			if (!rz_asm_is_bits(core->rasm, item->size)) {
				continue;
			}
			if (item->type != i) {
				continue;
			}
			rz_list_append(list, rz_str_dup(item->name));
		}
		const char *item_name;
		rz_list_foreach (list, iter, item_name) {
			value = rz_reg_getv(reg, item_name);
			rz_core_seek(core, value, true);
			rz_cons_printf("%s: ", item_name);
			RzCmdStatus cmd_res = handle_ts_stmt_tmpseek(state, command);
			UPDATE_CMD_STATUS_RES(res, cmd_res, err);
		}
	err:
		rz_list_free(list);
	}
	rz_core_seek(core, offorig, true);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_symbol_stmt) {
	RzCore *core = state->core;
	TSNode command = ts_node_named_child(node, 0);
	RzBinSymbol *sym;
	ut64 offorig = core->offset;
	ut64 obs = core->blocksize;
	RzBinObject *o = rz_bin_cur_object(core->bin);
	RzPVector *symbols = o ? (RzPVector *)rz_bin_object_get_symbols(o) : NULL;
	RzListIter *iter;
	void **it;
	rz_cons_break_push(NULL, NULL);
	RzList *lost = rz_list_newf(free);
	rz_pvector_foreach (symbols, it) {
		sym = *it;
		RzBinSymbol *bs = rz_mem_dup(sym, sizeof(RzBinSymbol));
		rz_list_append(lost, bs);
	}
	RzCmdStatus res = RZ_CMD_STATUS_OK;
	rz_list_foreach (lost, iter, sym) {
		if (rz_cons_is_breaked()) {
			break;
		}
		rz_core_block_size(core, sym->size);
		rz_core_seek(core, sym->vaddr, true);
		RzCmdStatus cmd_res = handle_ts_stmt_tmpseek(state, command);
		UPDATE_CMD_STATUS_RES(res, cmd_res, err);
	}
err:
	rz_cons_break_pop();
	rz_list_free(lost);
	rz_core_block_size(core, obs);
	rz_core_seek(core, offorig, true);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_string_stmt) {
	RzCore *core = state->core;
	TSNode command = ts_node_named_child(node, 0);

	RzBinObject *o = rz_bin_cur_object(core->bin);
	RzPVector *vec = o ? (RzPVector *)rz_bin_object_get_strings(o) : NULL;

	RzCmdStatus res = RZ_CMD_STATUS_OK;
	if (vec) {
		ut64 offorig = core->offset;
		ut64 obs = core->blocksize;
		RzBinString *s;
		RzList *lost = rz_list_newf(free);
		RzListIter *iter;
		void **it;
		rz_pvector_foreach (vec, it) {
			s = *it;
			RzBinString *bs = rz_mem_dup(s, sizeof(RzBinString));
			rz_list_append(lost, bs);
		}
		rz_list_foreach (lost, iter, s) {
			rz_core_block_size(core, s->size);
			rz_core_seek(core, s->vaddr, true);
			RzCmdStatus cmd_res = handle_ts_stmt_tmpseek(state, command);
			UPDATE_CMD_STATUS_RES(res, cmd_res, err);
		}
	err:
		rz_core_block_size(core, obs);
		rz_core_seek(core, offorig, true);
		rz_list_free(lost);
	}
	return res;
}

static RzCmdStatus do_iter_sections(struct tsr2cmd_state *state, TSNode node, bool show_sections) {
	RzCore *core = state->core;
	TSNode command = ts_node_named_child(node, 0);
	RzBinObject *obj = rz_bin_cur_object(core->bin);
	if (!obj) {
		return false;
	}
	RzCmdStatus res = RZ_CMD_STATUS_OK;
	ut64 offorig = core->offset;
	ut64 bszorig = core->blocksize;
	RzBinSection *sec;
	void **iter;
	rz_pvector_foreach (obj->sections, iter) {
		sec = *iter;
		if ((sec->is_segment && show_sections) || (!sec->is_segment && !show_sections)) {
			continue;
		}
		if (sec->vaddr == UT64_MAX) {
			continue;
		}
		rz_core_seek(core, sec->vaddr, true);
		rz_core_block_size(core, sec->vsize);
		RzCmdStatus cmd_res = handle_ts_stmt_tmpseek(state, command);
		UPDATE_CMD_STATUS_RES(res, cmd_res, err);
	}
err:
	rz_core_block_size(core, bszorig);
	rz_core_seek(core, offorig, true);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_sections_stmt) {
	return do_iter_sections(state, node, true);
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_segments_stmt) {
	return do_iter_sections(state, node, false);
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_iomap_stmt) {
	RzCore *core = state->core;
	TSNode command = ts_node_named_child(node, 0);
	int fd = rz_io_fd_get_current(core->io);
	// only iterate maps of current fd
	RzList *maps = rz_io_map_get_for_fd(core->io, fd);
	RzIOMap *map;
	RzCmdStatus res = RZ_CMD_STATUS_OK;
	if (maps) {
		RzListIter *iter;
		rz_list_foreach (maps, iter, map) {
			rz_core_seek(core, map->itv.addr, true);
			rz_core_block_size(core, map->itv.size);
			RzCmdStatus cmd_res = handle_ts_stmt_tmpseek(state, command);
			UPDATE_CMD_STATUS_RES(res, cmd_res, err);
		}
	err:
		rz_list_free(maps);
	}
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_dbgmap_stmt) {
	RzCore *core = state->core;
	TSNode command = ts_node_named_child(node, 0);
	RzDebug *dbg = core->dbg;
	RzCmdStatus res = RZ_CMD_STATUS_OK;
	if (dbg && dbg->cur && dbg->maps) {
		RzList *dbg_maps_addrs = rz_list_newf((RzListFree)free);
		if (!dbg_maps_addrs) {
			return RZ_CMD_STATUS_INVALID;
		}

		RzDebugMap *map;
		RzListIter *iter;
		rz_list_foreach (dbg->maps, iter, map) {
			int *val = RZ_NEW(int);
			*val = map->addr;
			rz_list_append(dbg_maps_addrs, val);
		}

		int *map_addr;
		rz_list_foreach (dbg_maps_addrs, iter, map_addr) {
			rz_core_seek(core, *map_addr, true);
			RzCmdStatus cmd_res = handle_ts_stmt_tmpseek(state, command);
			UPDATE_CMD_STATUS_RES(res, cmd_res, err);
		}
	err:
		rz_list_free(dbg_maps_addrs);
	}
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_function_stmt) {
	RzCore *core = state->core;
	TSNode command = ts_node_named_child(node, 0);
	TSNode filter_node = ts_node_named_child(node, 1);
	char *filter = NULL;
	if (!ts_node_is_null(filter_node)) {
		filter = ts_node_sub_string(filter_node, state->input);
	}
	ut64 obs = core->blocksize;
	ut64 offorig = core->offset;
	RzAnalysisFunction *fcn;
	RzList *list = rz_analysis_function_list(core->analysis);
	RzListIter *iter;
	RzCmdStatus res = RZ_CMD_STATUS_OK;
	rz_cons_break_push(NULL, NULL);
	rz_list_foreach (list, iter, fcn) {
		if (rz_cons_is_breaked()) {
			break;
		}
		if (!filter || rz_str_glob(fcn->name, filter)) {
			rz_core_seek(core, fcn->addr, true);
			rz_core_block_size(core, rz_analysis_function_linear_size(fcn));
			RzCmdStatus cmd_res = handle_ts_stmt_tmpseek(state, command);
			UPDATE_CMD_STATUS_RES(res, cmd_res, err);
		}
	}
err:
	rz_cons_break_pop();
	rz_core_block_size(core, obs);
	rz_core_seek(core, offorig, true);
	free(filter);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(iter_threads_stmt) {
	RzCore *core = state->core;
	TSNode command = ts_node_named_child(node, 0);
	RzDebug *dbg = core->dbg;
	RzCmdStatus res = RZ_CMD_STATUS_OK;
	if (dbg && dbg->cur && dbg->cur->threads) {
		int origtid = dbg->tid;
		RzDebugPid *p;
		RzList *list = dbg->cur->threads(dbg, dbg->pid);
		if (!list) {
			return RZ_CMD_STATUS_INVALID;
		}
		RzListIter *iter;
		rz_list_foreach (list, iter, p) {
			rz_debug_select(dbg, dbg->pid, p->pid);
			rz_cons_printf("PID %d\n", p->pid);
			RzCmdStatus cmd_res = handle_ts_stmt(state, command);
			UPDATE_CMD_STATUS_RES(res, cmd_res, err);
		}
	err:
		rz_debug_select(dbg, dbg->pid, origtid);
		rz_list_free(list);
	}
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(grep_stmt) {
	TSNode command = ts_node_child_by_field_name(node, "command", strlen("command"));
	TSNode arg = ts_node_child_by_field_name(node, "specifier", strlen("specifier"));
	char *arg_str = ts_node_handle_arg(state, node, arg, 1, false);
	if (!arg_str) {
		return RZ_CMD_STATUS_INVALID;
	}
	bool is_pipe = state->core->is_pipe;
	state->core->is_pipe = true;
	RzCmdStatus res = handle_ts_stmt(state, command);
	state->core->is_pipe = is_pipe;
	RZ_LOG_DEBUG("grep_stmt specifier: '%s'\n", arg_str);
	RzStrBuf *sb = rz_strbuf_new(arg_str);
	rz_strbuf_prepend(sb, "~");
	char *specifier_str_es = rz_cons_grep_strip(rz_strbuf_get(sb), "`");
	rz_strbuf_free(sb);
	char *specifier_str = rz_cmd_unescape_arg(specifier_str_es, true);
	RZ_LOG_DEBUG("grep_stmt processed specifier: '%s'\n", specifier_str);
	rz_cons_grep_process(specifier_str);
	free(specifier_str_es);
	free(arg_str);
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(html_disable_stmt) {
	TSNode command = ts_node_child_by_field_name(node, "command", strlen("command"));
	int scr_html = rz_config_get_i(state->core->config, "scr.html");
	rz_config_set_i(state->core->config, "scr.html", 0);
	int scr_color = rz_config_get_i(state->core->config, "scr.color");
	rz_config_set_i(state->core->config, "scr.color", COLOR_MODE_DISABLED);
	RzCmdStatus res = handle_ts_stmt(state, command);
	if (scr_html != -1) {
		rz_cons_flush();
		rz_config_set_i(state->core->config, "scr.html", scr_html);
	}
	if (scr_color != -1) {
		rz_config_set_i(state->core->config, "scr.color", scr_color);
	}
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(html_enable_stmt) {
	TSNode command = ts_node_child_by_field_name(node, "command", strlen("command"));
	int scr_html = rz_config_get_i(state->core->config, "scr.html");
	rz_config_set_i(state->core->config, "scr.html", true);
	RzCmdStatus res = handle_ts_stmt(state, command);
	if (scr_html != -1) {
		rz_cons_flush();
		rz_config_set_i(state->core->config, "scr.html", scr_html);
	}
	return res;
}

DEFINE_HANDLE_TS_FCN_AND_SYMBOL(pipe_stmt) {
	TSNode command_rizin = ts_node_named_child(node, 0);
	TSNode command_pipe = ts_node_named_child(node, 1);

	RzCmdStatus res = RZ_CMD_STATUS_INVALID;
	RzCmdParsedArgs *a = ts_node_handle_arg_prargs(state, node, command_pipe, 1, true, NULL, false);
	if (a && a->argc > 1) {
		res = core_cmd_pipe(state->core, state, command_rizin, a->argc - 1, a->argv + 1);
	}

	rz_cmd_parsed_args_free(a);
	return res;
}

static RzCmdStatus handle_ts_stmt(struct tsr2cmd_state *state, TSNode node) {
	RzCmdStatus ret = RZ_CMD_STATUS_INVALID;
	RzCore *core = state->core;
	RzCmd *cmd = core->rcmd;

	// NOTE: code copied from rz_core_cmd, needs rewrite when all the "remote"
	// concept is re-considered.
	if (core->cmdremote) {
		if (*state->input == 'q') {
			RZ_FREE(core->cmdremote);
			return RZ_CMD_STATUS_OK;
		} else if (*state->input != '=' && strncmp(state->input, "!=", 2)) {
			if (core->cmdremote[0]) {
				char *s = rz_str_newf("%s %s", core->cmdremote, state->input);
				rz_core_rtr_cmd(core, s);
				free(s);
			} else {
				char *res = rz_io_system(core->io, state->input);
				if (res) {
					rz_cons_printf("%s\n", res);
					free(res);
				}
			}
			return RZ_CMD_STATUS_OK;
		}
	}

	TSSymbol node_symbol = ts_node_symbol(node);
	ts_handler handler = ht_up_find(cmd->ts_symbols_ht, node_symbol, NULL);

	bool is_lastcmd = state->core->is_lastcmd;
	state->core->is_lastcmd = false;
	if (handler) {
		ret = handler(state, node);
	} else {
		RZ_LOG_WARN("No handler for this kind of command `%s`\n", ts_node_type(node));
	}
	if (state->log && !state->core->is_lastcmd) {
		free(state->core->lastcmd);
		state->core->lastcmd = ts_node_sub_string(node, state->input);
	}
	state->core->is_lastcmd = is_lastcmd;
	return ret;
}

static RzCmdStatus handle_ts_stmt_tmpseek(struct tsr2cmd_state *state, TSNode node) {
	// TODO: remove tmpseek when no commands will change behaviour based on `@` (tmpseek)
	RzCore *core = state->core;
	bool saved_tmpseek = core->tmpseek;
	core->tmpseek = true;
	RzCmdStatus ret = handle_ts_stmt(state, node);
	core->tmpseek = saved_tmpseek;
	return ret;
}

DEFINE_HANDLE_TS_FCN(statements) {
	RzCore *core = state->core;
	RzCmdStatus res = RZ_CMD_STATUS_OK;
	ut32 child_count = ts_node_named_child_count(node);
	int i;

	RZ_LOG_DEBUG("commands with %d childs\n", child_count);
	if (child_count == 0 && !*state->input) {
		if (core->cons->context->breaked) {
			core->cons->context->breaked = false;
			return RZ_CMD_STATUS_INVALID;
		}
		if (!core->cmdrepeat) {
			return RZ_CMD_STATUS_OK;
		}
		return rz_core_cmd_lastcmd_repeat(core, true) ? RZ_CMD_STATUS_OK : RZ_CMD_STATUS_INVALID;
	}
	if (state->split_lines) {
		rz_cons_break_push(NULL, NULL);
	}
	for (i = 0; i < child_count; i++) {
		if (core->cons->context->cmd_depth < 1) {
			RZ_LOG_ERROR("handle_ts_statements: That was too deep...\n");
			return RZ_CMD_STATUS_INVALID;
		}
		core->cons->context->cmd_depth--;
		if (core->max_cmd_depth - core->cons->context->cmd_depth == 1) {
			core->prompt_offset = core->offset;
		}

		if (state->split_lines && rz_cons_is_breaked()) {
			rz_cons_break_pop();
			return res;
		}
		TSNode command = ts_node_named_child(node, i);
		RzCmdStatus cmd_res = handle_ts_stmt(state, command);
		if (state->split_lines) {
			rz_cons_flush();
			rz_core_print_warnings_after(core);
			rz_core_task_yield(&core->tasks);
		}
		core->cons->context->cmd_depth++;
		if (cmd_res == RZ_CMD_STATUS_INVALID) {
			char *command_str = ts_node_sub_string(command, state->input);
			RZ_LOG_ERROR("core: Error while executing command: %s\n", command_str);
			free(command_str);
			res = cmd_res;
			goto err;
		} else if (cmd_res == RZ_CMD_STATUS_ERROR) {
			// make the whole script return ERROR, but continue to
			// execute the other commands
			res = cmd_res;
		} else if (cmd_res != RZ_CMD_STATUS_OK) {
			res = cmd_res;
			goto err;
		}
	}
err:
	if (state->split_lines) {
		rz_cons_break_pop();
	}
	return res;
}

#define HANDLER_RULE_OP(name) { #name, handle_ts_##name },
#define RULE_OP(name)

struct ts_data_symbol_map map_ts_stmt_handlers[] = {
#include "rz-shell-parser-cmds.inc"
	{ NULL, NULL },
};

#define RULE_OP(name)         { #name, &ts_##name##_symbol },
#define HANDLER_RULE_OP(name) RULE_OP(name)

struct ts_data_symbol_map map_ts_symbols[] = {
#include "rz-shell-parser-cmds.inc"
	{ NULL, NULL },
};

/**
 * \brief Create an instance of RzCmd for the Rizin language
 *
 * The new RzCmd is tied to the Rizin language specified in
 * subprojects/rizin-shell-parser.
 */
RZ_API RzCmd *rz_core_cmd_new(RzCore *core, bool has_cons) {
	RzCmd *res = rz_cmd_new(core, has_cons);
	if (!res) {
		return NULL;
	}

	TSLanguage *lang = tree_sitter_rzcmd();
	res->language = lang;
	res->ts_symbols_ht = ht_up_new(NULL, NULL);
	struct ts_data_symbol_map *entry = map_ts_stmt_handlers;
	while (entry->name) {
		TSSymbol symbol = ts_language_symbol_for_name(lang, entry->name, strlen(entry->name), true);
		ht_up_insert(res->ts_symbols_ht, symbol, entry->data);
		entry++;
	}

	entry = map_ts_symbols;
	while (entry->name) {
		TSSymbol *sym_ptr = entry->data;
		*sym_ptr = ts_language_symbol_for_name(lang, entry->name, strlen(entry->name), true);
		entry++;
	}
	return res;
}

static RzCmdStatus core_cmd_tsrzcmd(RzCore *core, const char *cstr, bool split_lines, bool log) {
	TSParser *parser = ts_parser_new();
	bool language_ok = ts_parser_set_language(parser, (TSLanguage *)core->rcmd->language);
	if (!language_ok) {
		rz_return_val_if_reached(RZ_CMD_STATUS_INVALID);
	}

	char *input = rz_str_dup(rz_str_trim_head_ro(cstr));

	TSTree *tree = ts_parser_parse_string(parser, NULL, input, strlen(input));
	if (!tree) {
		rz_warn_if_reached();
		free(input);
		return RZ_CMD_STATUS_INVALID;
	}

	TSNode root = ts_tree_root_node(tree);
	RzLine *line = core->cons->line;

	RzCmdStatus res = RZ_CMD_STATUS_INVALID;
	struct tsr2cmd_state state;
	state.parser = parser;
	state.core = core;
	state.input = input;
	state.tree = tree;
	state.log = log;
	state.split_lines = split_lines;
	rz_pvector_init(&state.saved_input, NULL);
	rz_pvector_init(&state.saved_tree, NULL);

	if (state.log && !has_push(root, state.input)) {
		rz_line_hist_add(line, state.input);
	}

	char *ts_str = ts_node_string(root);
	RZ_LOG_DEBUG("s-expr %s\n", ts_str);
	free(ts_str);

	if (is_ts_statements(root) && !ts_node_has_error(root)) {
		res = handle_ts_statements(&state, root);
	} else {
		// TODO: print a more meaningful error message and use the ERROR
		// tokens to indicate where, probably, the error is.
		RZ_LOG_ERROR("core: Error while parsing command: `%s`\n", input);
	}

	ts_tree_delete(tree);
	ts_parser_delete(parser);
	free(input);
	rz_pvector_fini(&state.saved_input);
	rz_pvector_fini(&state.saved_tree);
	return res;
}

RZ_API RzCmdStatus rz_core_cmd_rzshell(RzCore *core, const char *cstr, int log) {
	return core_cmd_tsrzcmd(core, cstr, false, log);
}

RZ_API int rz_core_cmd(RzCore *core, const char *cstr, int log) {
	return rz_cmd_status2int(core_cmd_tsrzcmd(core, cstr, false, log));
}

RZ_API RzCmdStatus rz_core_cmd_lines_rzshell(RzCore *core, const char *lines) {
	return core_cmd_tsrzcmd(core, lines, true, false);
}

RZ_API int rz_core_cmd_lines(RzCore *core, const char *lines) {
	RzCmdStatus status = core_cmd_tsrzcmd(core, lines, true, false);
	return status == RZ_CMD_STATUS_OK;
}

RZ_API int rz_core_cmd_file(RzCore *core, const char *file) {
	char *data = rz_file_abspath(file);
	if (!data) {
		return false;
	}
	char *odata = rz_file_slurp(data, NULL);
	free(data);
	if (!odata) {
		return false;
	}
	if (!rz_core_cmd_lines(core, odata)) {
		RZ_LOG_ERROR("core: Failed to run script '%s'\n", file);
		free(odata);
		return false;
	}
	free(odata);
	return true;
}

RZ_API int rz_core_cmd_command(RzCore *core, const char *command) {
	int ret, len;
	char *buf, *rcmd;
	rz_core_sysenv_begin(core);
	rcmd = buf = rz_sys_cmd_str(command, 0, &len);
	if (!buf) {
		return -1;
	}
	ret = rz_core_cmd(core, rcmd, 0);
	rz_core_sysenv_end(core);
	free(buf);
	return ret;
}

// TODO: Fix disasm loop is mandatory
RZ_API char *rz_core_disassemble_instr(RzCore *core, ut64 addr, int l) {
	char *cmd, *ret = NULL;
	cmd = rz_str_newf("pd %i @ 0x%08" PFMT64x, l, addr);
	if (cmd) {
		ret = rz_core_cmd_str(core, cmd);
		free(cmd);
	}
	return ret;
}

RZ_API char *rz_core_disassemble_bytes(RzCore *core, ut64 addr, int b) {
	char *cmd, *ret = NULL;
	cmd = rz_str_newf("pD %i @ 0x%08" PFMT64x, b, addr);
	if (cmd) {
		ret = rz_core_cmd_str(core, cmd);
		free(cmd);
	}
	return ret;
}

RZ_API int rz_core_cmd_buffer(RzCore *core, const char *buf) {
	char *ptr, *optr, *str = rz_str_dup(buf);
	if (!str) {
		return false;
	}
	optr = str;
	ptr = strchr(str, '\n');
	while (ptr) {
		*ptr = '\0';
		rz_core_cmd(core, optr, 0);
		optr = ptr + 1;
		ptr = strchr(str, '\n');
	}
	rz_core_cmd(core, optr, 0);
	free(str);
	return true;
}

RZ_API int rz_core_cmdf(RzCore *core, const char *fmt, ...) {
	char string[4096];
	int ret;
	va_list ap;
	va_start(ap, fmt);
	vsnprintf(string, sizeof(string), fmt, ap);
	ret = rz_core_cmd(core, string, 0);
	va_end(ap);
	return ret;
}

RZ_API RzCmdStatus rz_core_cmd0_rzshell(RzCore *core, const char *cmd) {
	return rz_core_cmd_rzshell(core, cmd, 0);
}

RZ_API int rz_core_cmd0(RzCore *core, const char *cmd) {
	return rz_core_cmd(core, cmd, 0);
}

RZ_API int rz_core_flush(RzCore *core, const char *cmd) {
	int ret = rz_core_cmd(core, cmd, 0);
	rz_cons_flush();
	return ret;
}

RZ_API char *rz_core_cmd_str_pipe(RzCore *core, const char *cmd) {
	char *tmp = NULL;
	const char *p = (*cmd != '"') ? strchr(cmd, '|') : NULL;
	if (!p && *cmd != '!' && *cmd != '.') {
		return rz_core_cmd_str(core, cmd);
	}
	rz_cons_reset();
	if (rz_file_mkstemp("cmd", &tmp) != -1) {
		RzConsPipe *cpipe = rz_cons_pipe_open(tmp, 1, 0);
		if (!cpipe) {
			rz_file_rm(tmp);
			free(tmp);
			return rz_core_cmd_str(core, cmd);
		}
		char *_cmd = rz_str_dup(cmd);
		rz_core_cmd(core, _cmd, 0);
		rz_cons_flush();
		rz_cons_pipe_close(cpipe);
		if (rz_file_exists(tmp)) {
			char *s = rz_file_slurp(tmp, NULL);
			rz_file_rm(tmp);
			free(tmp);
			free(_cmd);
			return s ? s : rz_str_dup("");
		}
		RZ_LOG_ERROR("core: slurp %s fails\n", tmp);
		rz_file_rm(tmp);
		free(tmp);
		free(_cmd);
		return rz_core_cmd_str(core, cmd);
	}
	return NULL;
}

RZ_API char *rz_core_cmd_strf(RzCore *core, const char *fmt, ...) {
	char string[4096];
	char *ret;
	va_list ap;
	va_start(ap, fmt);
	vsnprintf(string, sizeof(string), fmt, ap);
	ret = rz_core_cmd_str(core, string);
	va_end(ap);
	return ret;
}

static ut8 *core_cmd_raw(RzCore *core, const char *cmd, int *length) {
	const char *static_str;
	ut8 *retstr = NULL;
	rz_cons_push();
	bool is_pipe = core->is_pipe;
	core->is_pipe = true;
	if (rz_core_cmd(core, cmd, 0) == -1) {
		core->is_pipe = is_pipe;
		rz_cons_pop();
		return NULL;
	}
	core->is_pipe = is_pipe;
	rz_cons_filter();
	static_str = rz_cons_get_buffer();
	if (length) {
		int len = rz_cons_get_buffer_len();
		retstr = (ut8 *)rz_str_newlen(static_str, len);
		*length = len;
	} else {
		retstr = (ut8 *)rz_str_dup(rz_str_get(static_str));
	}

	rz_cons_pop();
	rz_cons_echo(NULL);
	return retstr;
}

/**
 * \brief Executes a rizin command and returns the stdout as a string
 */
RZ_API char *rz_core_cmd_str(RzCore *core, const char *cmd) {
	rz_return_val_if_fail(core && cmd, NULL);
	return (char *)core_cmd_raw(core, cmd, NULL);
}

/**
 * \brief Executes a rizin command and returns the raw stdout and its length
 */
RZ_API ut8 *rz_core_cmd_raw(RzCore *core, const char *cmd, int *length) {
	rz_return_val_if_fail(core && cmd && length, NULL);
	return core_cmd_raw(core, cmd, length);
}

static int compare_cmd_descriptor_name(const void *a, const void *b, void *user) {
	return strcmp(((RzCmdDescriptor *)a)->cmd, ((RzCmdDescriptor *)b)->cmd);
}

static void cmd_descriptor_init(RzCore *core) {
	const ut8 *p;
	RzListIter *iter;
	RzCmdDescriptor *x, *y;
	int n = core->cmd_descriptors->length;
	rz_list_sort(core->cmd_descriptors, compare_cmd_descriptor_name, NULL);
	rz_list_foreach (core->cmd_descriptors, iter, y) {
		if (--n < 0) {
			break;
		}
		x = &core->root_cmd_descriptor;
		for (p = (const ut8 *)y->cmd; *p; p++) {
			if (!x->sub[*p]) {
				if (p[1]) {
					RzCmdDescriptor *d = RZ_NEW0(RzCmdDescriptor);
					rz_list_append(core->cmd_descriptors, d);
					x->sub[*p] = d;
				} else {
					x->sub[*p] = y;
				}
			} else if (!p[1]) {
				RZ_LOG_ERROR("core: Command '%s' is duplicated, please check\n", y->cmd);
			}
			x = x->sub[*p];
		}
	}
}

RZ_API void rz_core_cmd_init(RzCore *core) {
	rz_return_if_fail(core);
	core->rcmd = rz_core_cmd_new(core, !!core->cons);
	core->rcmd->nullcallback = rz_core_cmd_nullcallback;
	core->cmd_descriptors = rz_list_newf(free);

	cmd_descriptor_init(core);
	rzshell_cmddescs_init(core);
}

RZ_IPI RzCmdStatus rz_basefind_compute_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) {
	ut32 pointer_size = 0;
	if (argc != 2 || RZ_STR_ISEMPTY(argv[1])) {
		pointer_size = 32;
	} else if (!strcmp(argv[1], "32")) {
		pointer_size = 32;
	} else if (!strcmp(argv[1], "64")) {
		pointer_size = 64;
	}
	return bool2status(rz_core_bin_basefind_print(core, pointer_size, state));
}

RZ_IPI RzCmdStatus rz_help_handler(RzCore *core, int argc, const char **argv) {
	bool use_color = rz_config_get_i(core->config, "scr.color") != 0;
	const char *cmd_color = use_color ? rz_cons_singleton()->context->pal.help : "";
	const char *cmd_bold = use_color ? rz_cons_singleton()->context->pal.btext : "";
	const char *reset = use_color ? rz_cons_singleton()->context->pal.reset : "";
	rz_cons_printf("Welcome to Rizin!\n\n");
	rz_cons_printf("%sFastest way to help yourself%s\n", cmd_bold, reset);
	rz_cons_printf("Type %s?**%s to search all commands' short descriptions.\n", cmd_color, reset);
	rz_cons_printf("Type %s?***%s to search all commands' long descriptions.\n", cmd_color, reset);
	rz_cons_printf("Type %s?**e%s to search all settings' descriptions.\n", cmd_color, reset);
	rz_cons_printf("\n");
	rz_cons_printf("Type %s?%s for a list of commands available.\n", cmd_color, reset);
	rz_cons_printf("Append %s?%s to any command to get the list of sub-commands or more details about a specific command.\n", cmd_color, reset);
	rz_cons_printf("Append %s??%s to any command to get the full description of a command, e.g. with examples.\n", cmd_color, reset);
	rz_cons_printf("\n");
	rz_cons_printf("Commands output can be redirected as in a regular shell, see %s>?%s for more info.\n", cmd_color, reset);
	rz_cons_printf("You can grep commands output with the 'internal grep', see %s~?%s for more info.\n", cmd_color, reset);
	rz_cons_printf("You can pipe an internal Rizin command to a system program, see %s|?%s for more info.\n", cmd_color, reset);
	rz_cons_printf("\n");
	rz_cons_printf("Chain multiple commands with %s;%s.\n", cmd_color, reset);
	rz_cons_printf("Temporary modifiers are your friends, see %s@?%s for more info, but here some useful ones:\n", cmd_color, reset);
	rz_cons_printf(" - %s@ %s temporarily switch to a different address\n", cmd_color, reset);
	rz_cons_printf(" - %s@a:<arch>%s temporarily switch to a different architecture\n", cmd_color, reset);
	rz_cons_printf(" - %s@e:<varname>=<varvalue>%s temporarily change an eval variable\n", cmd_color, reset);
	rz_cons_printf("\n");
	rz_cons_printf("There are a lot of settings that customize Rizin's behaviour, see them with %sel%s. Have a look at %se?%s to know how to interact with them.\n", cmd_color, reset, cmd_color, reset);
	rz_cons_printf("You can save your preferred settings in %s~/.rizinrc%s.\n", cmd_color, reset);
	return RZ_CMD_STATUS_OK;
}
