Add Lua REPL mode and multiline edit function in the mode.

Typing "@repl" after Ctrl-t r, enter Lua REPL mode.
Typing "@exit" in REPL mode will return you to normal mode.

Note:
- the determination of continuation lines is not done automatically, and
  if the end of a line is \, it is determined to be a continuation
  instruction.
- In REPL mode, tio's main loop is blocked. (Ctrl-t q works.)

Example:
>> t = {1,2,3,4,5}
>> for _,v in ipairs(t) do\
>>     print(v,"\r")\
>> end

Implementation improvements:
- Add tty_init() and script_interp_init() in order to only once
  initialization.
- Fix script function to work without device_fd.
This commit is contained in:
yabu76 2025-12-30 11:41:39 +09:00
parent c25a379fbb
commit 266338a926
5 changed files with 155 additions and 37 deletions

View file

@ -123,6 +123,12 @@ int main(int argc, char *argv[])
socket_configure(); socket_configure();
} }
/* Script interpreter init */
script_interp_init();
/* Initialize tty module once on program start */
tty_init();
/* Spawn input handling into separate thread */ /* Spawn input handling into separate thread */
tty_input_thread_create(); tty_input_thread_create();

View file

@ -44,7 +44,7 @@
#define MAX_BUFFER_SIZE 2000 // Maximum size of circular buffer #define MAX_BUFFER_SIZE 2000 // Maximum size of circular buffer
#define READ_LINE_SIZE 4096 // read_line buffer length #define READ_LINE_SIZE 4096 // read_line buffer length
static int device_fd; static int device_fd = 0;
static lua_State *script_interp = NULL; static lua_State *script_interp = NULL;
// clang-format off // clang-format off
@ -203,6 +203,11 @@ static int line_set(lua_State *L)
int cd = lua_tointeger(L, 5); int cd = lua_tointeger(L, 5);
int ri = lua_tointeger(L, 6); int ri = lua_tointeger(L, 6);
if (device_fd == 0)
{
return luaL_error(L, "tty device not ready");
}
if (dtr != -1) if (dtr != -1)
{ {
line_config[0].mask = TIOCM_DTR; line_config[0].mask = TIOCM_DTR;
@ -252,6 +257,11 @@ static int api_send(lua_State *L)
int protocol = luaL_checkinteger(L, 2); int protocol = luaL_checkinteger(L, 2);
int ret; int ret;
if (device_fd == 0)
{
return luaL_error(L, "tty device not ready");
}
if (file == NULL) if (file == NULL)
{ {
return 0; return 0;
@ -295,6 +305,11 @@ static int api_write(lua_State *L)
ssize_t ret; ssize_t ret;
int attempts = 100; int attempts = 100;
if (device_fd == 0)
{
return luaL_error(L, "tty device not ready");
}
do do
{ {
ret = write(device_fd, string, len); ret = write(device_fd, string, len);
@ -322,6 +337,11 @@ static int api_twrite(lua_State *L)
size_t len = 0; size_t len = 0;
const char *string = luaL_checklstring(L, 1, &len); const char *string = luaL_checklstring(L, 1, &len);
if (device_fd == 0)
{
return luaL_error(L, "tty device not ready");
}
for (; len > 0; --len, string++) for (; len > 0; --len, string++)
{ {
forward_to_tty(device_fd, *string); forward_to_tty(device_fd, *string);
@ -339,6 +359,11 @@ static int api_read(lua_State *L)
int size = luaL_checkinteger(L, 1); int size = luaL_checkinteger(L, 1);
int timeout = luaL_optinteger(L, 2, -1); // ms, negative value means forever. int timeout = luaL_optinteger(L, 2, -1); // ms, negative value means forever.
if (device_fd == 0)
{
return luaL_error(L, "tty device not ready");
}
luaL_Buffer buffer; luaL_Buffer buffer;
luaL_buffinit(L, &buffer); luaL_buffinit(L, &buffer);
@ -378,6 +403,11 @@ static int api_readline(lua_State *L)
luaL_Buffer b; luaL_Buffer b;
char ch; char ch;
if (device_fd == 0)
{
return luaL_error(L, "tty device not ready");
}
luaL_buffinit(L, &b); luaL_buffinit(L, &b);
luaL_prepbuffer(&b); luaL_prepbuffer(&b);
while (true) while (true)
@ -581,24 +611,36 @@ static lua_State *script_interp_new(void)
return L; return L;
} }
void script_run(int fd, const char *script_filename) void script_device_bind(int fd)
{
device_fd = fd;
}
void script_device_unbind(void)
{
device_fd = 0;
}
void script_do_line(const char *script_line)
{
assert(script_line != NULL);
assert(script_interp != NULL);
script_buffer_run(script_interp, script_line);
}
void script_run(const char *script_filename)
{ {
static bool doopt_by_nul = true; static bool doopt_by_nul = true;
device_fd = fd;
assert(script_filename != NULL); assert(script_filename != NULL);
assert(script_interp != NULL);
if (script_interp == NULL)
{
if (script_interp_new() == NULL)
return;
}
if (script_filename[0] == '\0') if (script_filename[0] == '\0')
{ {
if (doopt_by_nul) if (doopt_by_nul)
{ {
script_run_as_specified_by_options(fd); script_run_as_specified_by_options();
} }
return; return;
} }
@ -611,7 +653,7 @@ void script_run(int fd, const char *script_filename)
} }
else if (strcmp(script_filename, "@doopt") == 0) else if (strcmp(script_filename, "@doopt") == 0)
{ {
script_run_as_specified_by_options(fd); script_run_as_specified_by_options();
} }
else if (strcmp(script_filename, "@nuldo=opt") == 0) else if (strcmp(script_filename, "@nuldo=opt") == 0)
{ {
@ -643,15 +685,9 @@ void script_run(int fd, const char *script_filename)
} }
} }
void script_run_as_specified_by_options(int fd) void script_run_as_specified_by_options(void)
{ {
device_fd = fd; assert(script_interp != NULL);
if (script_interp == NULL)
{
if (script_interp_new() == NULL)
return;
}
if (option.script_filename != NULL) if (option.script_filename != NULL)
{ {
@ -682,3 +718,12 @@ const char *script_run_state_to_string(script_run_t state)
return "Unknown"; return "Unknown";
} }
} }
void script_interp_init(void)
{
if (script_interp_new() == NULL)
{
tio_error_printf("Could not start script interpreter.");
exit(EXIT_FAILURE);
}
}

View file

@ -29,6 +29,10 @@ typedef enum
SCRIPT_RUN_END, SCRIPT_RUN_END,
} script_run_t; } script_run_t;
void script_run(int fd, const char *script_filename); void script_interp_init(void);
void script_run_as_specified_by_options(int fd); void script_device_bind(int fd);
void script_device_unbind(void);
void script_run(const char *script_filename);
void script_run_as_specified_by_options(void);
void script_do_line(const char *script_line);
const char *script_run_state_to_string(script_run_t state); const char *script_run_state_to_string(script_run_t state);

View file

@ -147,6 +147,8 @@ typedef enum
SUBCOMMAND_MAP, SUBCOMMAND_MAP,
} sub_command_t; } sub_command_t;
#define MLINE_MAX 4096
// clang-format off // clang-format off
const char random_array[] = const char random_array[] =
{ {
@ -183,7 +185,7 @@ static char *tty_buffer_write_ptr = tty_buffer;
static pthread_t thread; static pthread_t thread;
static int pipefd[2]; static int pipefd[2];
static pthread_mutex_t mutex_input_ready = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t mutex_input_ready = PTHREAD_MUTEX_INITIALIZER;
static char line[PATH_MAX]; static char line[PATH_MAX], mline[MLINE_MAX];
static size_t listing_device_name_length_max = 0; static size_t listing_device_name_length_max = 0;
static readline_t *readline_ctx = NULL; static readline_t *readline_ctx = NULL;
static readline_t *subcmd_readline_ctx = NULL; static readline_t *subcmd_readline_ctx = NULL;
@ -593,7 +595,9 @@ static void tty_line_poke(int fd, int mask, tty_line_mode_t mode, unsigned int d
static int tio_subcmd_readln(const char *title_prompt) static int tio_subcmd_readln(const char *title_prompt)
{ {
if (title_prompt && (title_prompt[0] != '\0')) {
tio_printf_raw("%s\r\n", title_prompt); tio_printf_raw("%s\r\n", title_prompt);
}
readline_prompt_for_input(subcmd_readline_ctx); readline_prompt_for_input(subcmd_readline_ctx);
/* Read line with line edit and history. */ /* Read line with line edit and history. */
@ -660,6 +664,50 @@ static void mappings_print(void)
// clang-format on // clang-format on
} }
static void handle_script_repl(void)
{
bool local_echo_bkup = option.local_echo;
int line_len;
int mline_len = 0;
option.local_echo = true;
tio_printf("Enter Lua REPL mode (@exit to exit)");
strcpy(mline, "");
while (true)
{
tio_subcmd_readln("");
if (strcmp(line, "@exit") == 0)
break;
line_len = strlen(line);
if (line_len > 0)
{
if (mline_len + line_len + 1 > MLINE_MAX)
{
tio_printf("Too long lines. The size should be lesser then %d bytes", MLINE_MAX);
strcpy(mline, "");
mline_len = 0;
continue;
}
strcat(&mline[mline_len], line);
mline_len += line_len;
if (mline_len > 0 && mline[mline_len - 1] == '\\')
{
mline[mline_len - 1] = '\n';
continue;
}
}
script_do_line(mline);
strcpy(mline, "");
mline_len = 0;
}
option.local_echo = local_echo_bkup;
}
void handle_command_sequence(char input_char, char *output_char, bool *forward) void handle_command_sequence(char input_char, char *output_char, bool *forward)
{ {
char unused_char; char unused_char;
@ -1079,8 +1127,15 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
/* Run script */ /* Run script */
tio_printf("Run Lua script"); tio_printf("Run Lua script");
tio_subcmd_readln("Enter file name or \"!\" lua commands or \"@\" direction to interpreter: "); tio_subcmd_readln("Enter file name or \"!\" lua commands or \"@\" direction to interpreter: ");
if (strcmp(line, "@repl") == 0)
{
handle_script_repl();
}
else
{
clear_line(); clear_line();
script_run(device_fd, line); script_run(line);
}
break; break;
case KEY_SHIFT_R: case KEY_SHIFT_R:
@ -2545,6 +2600,21 @@ void forward_to_tty(int fd, char output_char)
} }
} }
void tty_init(void)
{
// Initialize readline like history
readline_ctx = readline_create();
subcmd_readline_ctx = readline_create();
if (readline_ctx == NULL || subcmd_readline_ctx == NULL)
{
tio_error_printf("Could not allocate readline buffer.");
exit(EXIT_FAILURE);
}
readline_set_prompt(readline_ctx, "> ");
readline_set_prompt(subcmd_readline_ctx, ">> ");
}
int tty_connect(void) int tty_connect(void)
{ {
fd_set rdfs; /* Read file descriptor set */ fd_set rdfs; /* Read file descriptor set */
@ -2676,9 +2746,11 @@ int tty_connect(void)
} }
/* Manage script activation */ /* Manage script activation */
script_device_bind(device_fd);
if (option.script_run != SCRIPT_RUN_NEVER) if (option.script_run != SCRIPT_RUN_NEVER)
{ {
script_run_as_specified_by_options(device_fd); script_run_as_specified_by_options();
if (option.script_run == SCRIPT_RUN_ONCE) if (option.script_run == SCRIPT_RUN_ONCE)
{ {
@ -2698,17 +2770,6 @@ int tty_connect(void)
exit(status); exit(status);
} }
// Initialize readline like history
readline_ctx = readline_create();
subcmd_readline_ctx = readline_create();
if (readline_ctx == NULL || subcmd_readline_ctx == NULL)
{
tio_error_printf("Could not allocate readline buffer.");
exit(EXIT_FAILURE);
}
readline_set_prompt(readline_ctx, "> ");
readline_set_prompt(subcmd_readline_ctx, ">> ");
/* Input loop */ /* Input loop */
while (true) while (true)
{ {
@ -3035,6 +3096,7 @@ error_setspeed:
error_tcsetattr: error_tcsetattr:
error_tcgetattr: error_tcgetattr:
error_read: error_read:
script_device_unbind();
tty_disconnect(); tty_disconnect();
error_open: error_open:
return TIO_ERROR; return TIO_ERROR;

View file

@ -76,6 +76,7 @@ void stdout_configure(void);
void stdin_configure(void); void stdin_configure(void);
void tty_configure(void); void tty_configure(void);
void tty_reconfigure(void); void tty_reconfigure(void);
void tty_init(void);
int tty_connect(void); int tty_connect(void);
void tty_wait_for_device(void); void tty_wait_for_device(void);
void list_serial_devices(void); void list_serial_devices(void);