summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Albers <thomas@thomaslabs.org>2023-08-06 16:04:59 +0200
committerThomas Albers <thomas@thomaslabs.org>2023-08-06 16:04:59 +0200
commit30125839213e5b81e87c4fa3d7d3f4030b3659e3 (patch)
tree183670f0617059ac7b07927adcdcc9908f4c97aa
parentd0e2b015f25d53498da258d3ee988a101bc597fa (diff)
Merge repl.c and cmd.c commands
-rw-r--r--Makefile2
-rw-r--r--cmd.c468
-rw-r--r--repl.c356
-rw-r--r--zup.c15
-rw-r--r--zup.h3
5 files changed, 365 insertions, 479 deletions
diff --git a/Makefile b/Makefile
index 8e1c0dd..38084b9 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
TARGET = zup
PREFIX = /usr
-SRC = crc16.c serial.c repl.c cmd.c zup.c
+SRC = crc16.c serial.c cmd.c zup.c
OBJ = $(SRC:%.c=%.o)
CFLAGS = -Werror -Wpedantic -I. -g -DUSE_READLINE
diff --git a/cmd.c b/cmd.c
index c4a7d20..aa8beff 100644
--- a/cmd.c
+++ b/cmd.c
@@ -3,199 +3,429 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <signal.h>
#include <errno.h>
-typedef int (*cmd_command)(int, const struct param*, int, char**);
-
+#ifdef USE_READLINE
+#include <readline/readline.h>
+#include <readline/history.h>
+#endif // USE_READLINE
+
+struct command;
+
+enum args_type {
+ A_END,
+ A_BYTE,
+ A_DBYTE,
+ A_QBYTE,
+ A_OBYTE,
+ A_STRING,
+ A_OPTIONAL = 16
+};
+
+typedef int (*fptr)(int, const struct command *, const struct param *,
+ int, char **);
+
+struct command {
+ char alias;
+ const char *name;
+ int *args_spec;
+ const char *usage;
+ fptr fptr;
+ int quit;
+};
+
+#define ARGS(...) (int []){__VA_ARGS__}
+
+// FIXME: Assert ranges for integers
static int
-cmd_boot(int fd, const struct param *param, int argc, char **args)
+parse_args(const struct command *cmd, int argc, char **args, ...)
{
+ int i = 0;
+ int opt = 0;
int err = 0;
- uint8_t bank;
- uint16_t address;
+ va_list ap;
+ va_start(ap, args);
+
+ void *ptr = NULL;
+ int *p = cmd->args_spec;
+ for ( ; *p != A_END; ++p) {
+ // Avoid runaway p
+ assert(i < MAX_ARGS);
+
+ if (*p == A_OPTIONAL) {
+ opt = 1;
+ continue;
+ }
- if (argc != 2) {
- fputs("BOOT expects 2 arguments\n", stderr);
- return 1;
- }
+ if (i == argc) {
+ if (!opt)
+ goto syntax_error;
+ break;
+ }
- errno = 0;
- bank = strtoul(args[0], NULL, 16);
- if (errno)
- goto error;
+ ptr = va_arg(ap, void *);
+ errno = 0;
+ char *end = NULL;
- address = strtoul(args[1], NULL, 16);
- if (errno)
- goto error;
+ switch (*p) {
+ case A_BYTE:
+ *((uint8_t *)ptr) = strtoul(args[i], &end, 16);
+ break;
- return z_boot(fd, bank, address);
+ case A_DBYTE:
+ *((uint16_t *)ptr) = strtoul(args[i], &end, 16);
+ break;
-error:
- perror("BOOT: Invalid arguments\n");
+ case A_STRING:
+ *((const char **)ptr) = args[i];
+ break;
+ }
+
+ if (errno || args[i] == end) {
+ fprintf(stderr, "Invalid argument: %s\n", args[i]);
+ goto syntax_error;
+ }
+
+ ++i;
+ }
+
+ va_end(ap);
+ return i;
+
+syntax_error:
+ va_end(ap);
+ fputs(cmd->usage, stderr);
return -1;
}
static int
-cmd_read(int fd, const struct param *param, int argc, char **args)
+cmd_boot(int fd, const struct command *cmd, const struct param *param,
+ int argc, char **args)
{
- int err = 0;
-
+ int err;
uint8_t bank;
uint16_t address;
- uint16_t length;
- const char *pathname;
- if (!((argc == 4 && !param->human_readable)
- || (argc == 3 && param->human_readable))) {
- fputs("READ expects 4 arguments\n", stderr);
- return 1;
- }
+ if ((err = parse_args(cmd, argc, args, &bank, &address) < 0))
+ return err;
- errno = 0;
- bank = strtoul(args[0], NULL, 16);
- err |= errno;
- errno = 0;
- address = strtoul(args[1], NULL, 16);
- err |= errno;
+ return z_boot(fd, bank, address);
+}
- errno = 0;
- length = strtoul(args[2], NULL, 16);
- err |= errno;
+static int
+cmd_read(int fd, const struct command *cmd, const struct param *param,
+ int argc, char **args)
+{
+ int err = 0;
+ uint8_t buf[MAX_PACKET_SIZE];
- pathname = args[3];
+ uint8_t bank;
+ uint16_t address;
+ uint16_t length;
+ const char *pathname = NULL;
- if (err) {
- fputs("READ: Invalid arguments\n", stderr);
+ err = parse_args(cmd, argc, args, &bank, &address, &length, &pathname);
+ if (err < 0)
return -1;
- }
- uint8_t buf[MAX_PACKET_SIZE];
- if ((err = z_read(fd, bank, address, length, buf)))
+ if ((err = z_read(fd, bank, address, length, buf))) {
+ print_error(err);
return err;
+ }
if (param->human_readable) {
hexdump(address, length, buf);
- } else {
- FILE *file;
- if (!(file = fopen(pathname, "w"))) {
+ } else if (pathname) {
+ FILE *fp;
+ if (!(fp = fopen(pathname, "w"))) {
perror("File could not be opened");
return -1;
}
- fwrite(buf, sizeof(uint8_t), length, file);
- fclose(file);
+ fwrite(buf, sizeof(uint8_t), length, fp);
+ fclose(fp);
+ } else {
+ fwrite(buf, sizeof(uint8_t), length, stdout);
+ fflush(stdout);
}
return 0;
}
-// TODO: Handle length 0 at protcol level
static int
-cmd_write(int fd, const struct param *param, int argc, char **args)
+cmd_write(int fd, const struct command *cmd, const struct param *param,
+ int argc, char **args)
{
- int err = 0;
+ int err;
+ char line[512];
+ uint8_t buf[MAX_PACKET_SIZE];
+
+ char *p;
uint8_t bank;
uint16_t address;
- uint16_t length;
- const char *pathname;
+ uint16_t length = 0;
+ const char *pathname = NULL;
- if (argc != 3) {
- fputs("WRITE expects 3 arguments\n", stderr);
+ err = parse_args(cmd, argc, args, &bank, &address, &pathname);
+ if (err < 0)
return -1;
- }
- errno = 0;
- bank = strtoul(args[0], NULL, 16);
- err |= errno;
+ if (param->human_readable) {
+ do {
+ // TODO: Handle fgets error
+ fgets(line, LEN(line), stdin);
+ if (line && !strcmp("\n", line))
+ break;
- errno = 0;
- address = strtoul(args[1], NULL, 16);
- err |= errno;
+ p = strtok(line, " \n");
+ while (p && length < 10) {
+ errno = 0;
+ buf[length++] = strtoul(p, NULL, 16);
+ p = strtok(NULL, " \n");
+ }
- pathname = args[2];
+ if (p) {
+ fputs("WRITE: Input can't be longer than 256 bytes\n", stderr);
+ return -1;
+ }
+ } while (1);
- if (err) {
- fputs("WRITE: Invalid arguments\n", stderr);
- return -1;
+ if ((err = z_write(fd, bank, address, length, buf))) {
+ print_error(err);
+ return err;
+ }
+ } else {
+ FILE *fp = pathname ? fopen(pathname, "r") : stdin;
+
+ if (!fp) {
+ perror("File could not be opened");
+ return -1;
+ }
+
+ // FIXME: fread MAX_PACKET_SIZE insted of 8
+ // Split file into packets
+ while ((length = fread(buf, sizeof(uint8_t), 8, fp))) {
+ if ((err = z_write(fd, bank, address, length, buf)))
+ break;
+ address += length;
+ }
+
+ if (fp != stdin)
+ fclose(fp);
}
- uint8_t buf[MAX_PACKET_SIZE];
- FILE *fp;
+ return err;
+}
- if (!(fp = fopen(pathname, "r"))) {
- perror("File could not be opened");
+static int
+cmd_echo(int fd, const struct command *cmd, const struct param *param,
+ int argc, char **args)
+{
+ int err;
+ char line[512];
+ char buf[MAX_PACKET_SIZE];
+
+ if (!param->human_readable) {
+ fputs("Echo is a interactive command only", stderr);
return -1;
}
- // Split file into packets
- while ((length = fread(buf, sizeof(uint8_t), 8, fp))) {
- if ((err = z_write(fd, bank, address, length, buf)))
+ parse_args(cmd, argc, args);
+
+ memset(line, 0, sizeof(line));
+ memset(buf, 0, sizeof(buf));
+
+ size_t length = 0;
+
+ do {
+ // TODO: Handle fgets error
+ fgets(line, LEN(line), stdin);
+ if (line && !strcmp("\n", line))
break;
- address += length;
+
+ size_t len = strlen(line);
+ if (line[len - 1] == '\n')
+ line[--len] = '\0';
+
+ if (length + len >= MAX_PACKET_SIZE) {
+ fputs("ECHO: Input can't be longer than 256 bytes\n", stderr);
+ return -1;
+ }
+
+ strcat(buf, line);
+ length += len;
+ } while (1);
+
+ if ((err = z_echo(fd, length, (uint8_t *)buf))) {
+ print_error(err);
+ return err;
}
- fclose(fp);
+ fwrite(buf, sizeof(uint8_t), length, stdout);
+ putchar('\n');
+ fflush(stdout);
- return err;
+ return 0;
}
static int
-cmd_io_read(int fd, const struct param *param, int argc, char **args)
+repl_io_write(int fd, const struct command *cmd, const struct param *param,
+ int argc, char **args)
{
return 0;
}
static int
-cmd_io_write(int fd, const struct param *param, int argc, char **args)
+repl_io_read(int fd, const struct command *cmd, const struct param *param,
+ int argc, char **args)
{
return 0;
}
+/*
+static volatile sig_atomic_t quit = 0;
+
+void
+interrupt_handler(int signal)
+{
+ puts("Bye!");
+ quit = 1;
+ // fclose(stdin);
+}
+*/
+
+const struct command cmd_table[] = {
+ {
+ .alias = 'b',
+ .name = "boot",
+ .fptr = cmd_boot,
+ .quit = 0,
+ .args_spec = ARGS(A_BYTE, A_DBYTE, A_END),
+ .usage = "Usage: boot <bank> <address>\n"
+ },
+ {
+ .alias = 'r',
+ .name = "read",
+ .fptr = cmd_read,
+ .quit = 0,
+ .args_spec = ARGS(A_BYTE, A_DBYTE, A_DBYTE, A_OPTIONAL, A_STRING, A_END),
+ .usage = "Usage: read <bank> <address> <length> [pathname]\n"
+ },
+ {
+ .alias = 'w',
+ .name = "write",
+ .fptr = cmd_write,
+ .quit = 0,
+ .args_spec = ARGS(A_BYTE, A_DBYTE, A_OPTIONAL, A_STRING, A_END),
+ .usage = "Usage: write <bank> <address> [pathname]\n"
+ },
+ {
+ .alias = 'i',
+ .name = "io_read",
+ .fptr = NULL,
+ .quit = 0,
+ .args_spec = ARGS(A_DBYTE, A_DBYTE, A_END),
+ .usage = "Usage: io_read <address> <length>\n"
+ },
+ {
+ .alias = 'o',
+ .name = "io_write",
+ .fptr = NULL,
+ .quit = 0,
+ .args_spec = ARGS(A_DBYTE, A_END),
+ .usage = "Usage: io_write <address>\n"
+ },
+ {
+ .alias = 'e',
+ .name = "echo",
+ .fptr = cmd_echo,
+ .quit = 0,
+ .args_spec = ARGS(A_END),
+ .usage = ""
+ },
+ {
+ .alias = 'q',
+ .name = "quit",
+ .fptr = NULL,
+ .quit = 1,
+ .args_spec = ARGS(A_END),
+ .usage = ""
+ }
+};
+
int
-run_commands(int fd, const struct param *param, int ncmd, char **cmds)
+run_line(int fd, struct param *param, char *line, const char *sep)
{
- struct command {
- const char *name;
- cmd_command fptr;
- };
-
- const struct command cmd_table[] = {
- {"b", cmd_boot},
- {"r", cmd_read},
- {"w", cmd_write},
- {"i", cmd_io_read},
- {"o", cmd_io_write}
- };
-
- for (int i = 0; i < ncmd; ++i) {
- int err;
- int argc = 1;
- char *args[MAX_ARGS] = { NULL };
-
- // Parse command
- args[0] = strtok(cmds[i], ":");
- while(argc < MAX_ARGS && (args[argc] = strtok(NULL, ":")))
- ++argc;
-
- // Match action
- int cmdi;
- for (cmdi = 0; cmdi < LEN(cmd_table); ++cmdi) {
- if (!strcmp(args[0], cmd_table[cmdi].name))
- break;
- }
+ int err;
+ int argc = 1;
+ char *args[MAX_ARGS + 1] = { NULL };
+ fptr p;
+
+ // Tokenize command
+ args[0] = strtok(line, sep);
+ if (!args[0])
+ return 0;
+ while(argc < LEN(args) && (args[argc] = strtok(NULL, sep)))
+ ++argc;
+
+ // Match action
+ int cmdi;
+ for (cmdi = 0; cmdi < LEN(cmd_table); ++cmdi) {
+ if ((!args[0][1] && args[0][0] == cmd_table[cmdi].alias)
+ || !strcmp(args[0], cmd_table[cmdi].name))
+ break;
+ }
- // Call action
- if (cmdi == LEN(cmd_table)) {
- fprintf(stderr, "Unknown command %s\n", args[0]);
- return 1;
- }
+ // Call action
+ if (cmdi == LEN(cmd_table)) {
+ fprintf(stdout, "Unknown command %s\n", args[0]);
+ return -1;
+ }
- if ((err = cmd_table[cmdi].fptr(fd, param, argc - 1, &args[1]))) {
- print_error(err);
- return err;
- }
+ if ((p = cmd_table[cmdi].fptr)
+ && (err = p(fd, &cmd_table[cmdi], param, argc - 1, &args[1]))) {
+ print_error(err);
+ return err;
}
- return 0;
+ return cmd_table[cmdi].quit;
+}
+
+// TODO: Handle signals (CTRL-C)
+void
+repl(int fd, struct param *param)
+{
+ int quit = 0;
+
+#ifdef USE_READLINE
+ using_history();
+#endif
+
+ while (!quit) {
+#ifdef USE_READLINE
+ char *line = readline("* ");
+
+ if (!line)
+ break;
+
+ add_history(line);
+ quit = run_line(fd, param, line, " ");
+ free(line);
+#else
+ size_t len = 0;
+ char line[512];
+ fputs("* ", stdout);
+
+ if (!fgets(line, sizeof(line), stdin))
+ break;
+
+ len = strlen(line);
+ if (line[len - 1] == '\n')
+ line[len - 1] = '\0';
+
+ quit = run_line(fd, param, line, " ");
+#endif
+ }
}
diff --git a/repl.c b/repl.c
deleted file mode 100644
index edac70f..0000000
--- a/repl.c
+++ /dev/null
@@ -1,356 +0,0 @@
-#include <zup.h>
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <signal.h>
-#include <errno.h>
-
-#ifdef USE_READLINE
-#include <readline/readline.h>
-#include <readline/history.h>
-#endif // USE_READLINE
-
-typedef int (*repl_command)(int, const struct param*, int, char**);
-
-static int
-repl_read(int fd, const struct param *param, int argc, char **args)
-{
- int err = 0;
- uint8_t buf[MAX_PACKET_SIZE];
-
- uint8_t bank;
- uint16_t address;
- uint16_t length;
- const char *pathname = NULL;
-
- if (argc != 3 && argc != 4)
- goto syntax_error;
-
- errno = 0;
- bank = strtoul(args[0], NULL, 16);
- err |= errno;
-
- errno = 0;
- address = strtoul(args[1], NULL, 16);
- err |= errno;
-
- errno = 0;
- length = strtoul(args[2], NULL, 16);
- err |= errno;
-
- if (argc == 4)
- pathname = args[3];
-
- if (err)
- goto syntax_error;
-
- if ((err = z_read(fd, bank, address, length, buf))) {
- print_error(err);
- return err;
- }
-
- hexdump(address, length, buf);
-
- if (pathname) {
- FILE *file;
- if (!(file = fopen(pathname, "w"))) {
- perror("File could not be opened");
- return -1;
- }
- fwrite(buf, sizeof(uint8_t), length, file);
- fclose(file);
- }
-
- return err;
-
-syntax_error:
- fputs("Usage: read <bank> <address> <length> [pathname]\n", stderr);
- return 1;
-}
-
-static int
-repl_write(int fd, const struct param *param, int argc, char **args)
-{
- int err;
- char line[512];
- uint8_t buf[MAX_PACKET_SIZE];
-
- char *p;
-
- uint8_t bank;
- uint16_t address;
- uint16_t length = 0;
-
- do {
- // TODO: Handle fgets error
- fgets(line, LEN(line), stdin);
- if (line && !strcmp("\n", line))
- break;
-
- p = strtok(line, " \n");
- while (p && length < 10) {
- errno = 0;
- buf[length++] = strtoul(p, NULL, 16);
- p = strtok(NULL, " \n");
- }
-
- if (p) {
- fputs("WRITE: Input can't be longer than 256 bytes\n", stderr);
- return -1;
- }
- } while (1);
-
- if ((err = z_write(fd, bank, address, length, buf))) {
- print_error(err);
- return err;
- }
-
- return 0;
-}
-
-static int
-repl_echo(int fd, const struct param *param, int argc, char **args)
-{
- int err;
- char line[512];
- char buf[MAX_PACKET_SIZE];
-
- memset(line, 0, sizeof(line));
- memset(buf, 0, sizeof(buf));
-
- size_t length = 0;
-
- do {
- // TODO: Handle fgets error
- fgets(line, LEN(line), stdin);
- if (line && !strcmp("\n", line))
- break;
-
- size_t len = strlen(line);
- if (line[len - 1] == '\n')
- line[--len] = '\0';
-
- if (length + len >= MAX_PACKET_SIZE) {
- fputs("ECHO: Input can't be longer than 256 bytes\n", stderr);
- return -1;
- }
-
- strcat(buf, line);
- length += len;
- } while (1);
-
- if ((err = z_echo(fd, length, (uint8_t *)buf))) {
- print_error(err);
- return err;
- }
-
- fwrite(buf, sizeof(uint8_t), length, stdout);
- putchar('\n');
- fflush(stdout);
-
- return 0;
-}
-
-static int
-repl_io_write(int fd, const struct param *param, int argc, char **args)
-{
- return 0;
-}
-
-/*
-static volatile sig_atomic_t quit = 0;
-
-void
-interrupt_handler(int signal)
-{
- puts("Bye!");
- quit = 1;
- // fclose(stdin);
-}
-*/
-
-enum args_type {
- A_END,
- A_BYTE,
- A_DBYTE,
- A_QBYTE,
- A_OBYTE,
- A_STRING,
- A_OPTIONAL = 16
-};
-
-struct command;
-
-typedef int (*fptr)(int, const struct command *, struct param *, int, char **);
-
-struct command {
- char alias;
- const char *name;
- int *args_spec;
- const char *usage;
- fptr fptr;
- int quit;
-};
-
-static int
-parse_args(const struct command *cmd, int argc, char **args, ...)
-{
- int i = 0;
- int opt = 0;
- int err = 0;
- va_list ap;
- va_start(ap, args);
-
- void *ptr = NULL;
- int *p = cmd->args_spec;
-
- for ( ; *p != A_END; ++p) {
- // Avoid runaway p
- assert(i < MAX_ARGS);
-
- if (*p == A_OPTIONAL) {
- opt = 1;
- continue;
- }
-
- if (i == argc && !opt)
- goto syntax_error;
-
- ptr = va_arg(ap, void *);
- errno = 0;
- char *end = NULL;
-
- switch (*p) {
- case A_BYTE:
- *((uint8_t *)ptr) = strtoul(args[i], &end, 16);
- break;
-
- case A_DBYTE:
- *((uint16_t *)ptr) = strtoul(args[i], &end, 16);
- break;
-
- case A_STRING:
- *((const char **)ptr) = args[i];
- break;
- }
-
- if (errno || args[i] == end) {
- fprintf(stderr, "Invalid argument: %s\n", args[i]);
- goto syntax_error;
- }
-
- ++i;
- }
-
- va_end(ap);
- return i;
-
-syntax_error:
- va_end(ap);
- fputs(cmd->usage, stderr);
- return -1;
-}
-
-#define ARGS(...) (int []){__VA_ARGS__}
-
-static int
-repl_io_read(int fd, const struct param *param, int argc, char **args)
-{
- uint8_t bank;
- uint16_t address;
- const char *filename = NULL;
-
- struct command cmd = {
- .alias = 'i',
- .name = "io_read",
- .usage = "",
- .fptr = NULL,
- .quit = 0,
- .args_spec = ARGS(A_BYTE, A_DBYTE, A_OPTIONAL, A_STRING, A_END),
- .usage = "Usage: read <bank> <address> <length> [pathname]\n"
- };
-
- parse_args(&cmd, argc, args, &bank, &address, &filename);
- printf("result: %hhx %hx %s\n", bank, address, filename);
- return 0;
-}
-
-// TODO: Handle signals (CTRL-C)
-// TODO: Print errors when in repl and crash if from terminal
-void
-repl(int fd, struct param *param)
-{
- struct command {
- const char *name;
- repl_command fptr;
- int quit;
- };
-
- const struct command cmd_table[] = {
- {"read", repl_read, 0},
- {"write", repl_write, 0},
- {"echo", repl_echo, 0},
- {"io_read", repl_io_read, 0},
- {"io_write", repl_io_write, 0},
- {"quit", NULL, 1}
- };
-
- int quit = 0;
-
-#ifdef USE_READLINE
- using_history();
-#endif
-
- while (!quit) {
-#ifdef USE_READLINE
- char *line = readline("* ");
-
- if (!line)
- break;
-
- add_history(line);
-#else
- size_t len = 0;
- char line[512];
- fputs("* ", stdout);
-
- if (!fgets(line, sizeof(line), stdin))
- break;
-
- len = strlen(line);
- if (line[len - 1] == '\n')
- line[len - 1] = '\0';
-#endif
- int argc = 1;
- char *args[MAX_ARGS] = { NULL };
-
- // Parse command
- args[0] = strtok(line, " ");
- if (!args[0])
- goto skip;
- while(argc < MAX_ARGS && (args[argc] = strtok(NULL, " ")))
- ++argc;
-
- // Match action
- int cmdi;
- for (cmdi = 0; cmdi < LEN(cmd_table); ++cmdi) {
- if (!strcmp(args[0], cmd_table[cmdi].name))
- break;
- }
-
- // Call action
- if (cmdi == LEN(cmd_table)) {
- fprintf(stdout, "Unknown command %s\n", args[0]);
- } else {
- repl_command fptr = cmd_table[cmdi].fptr;
- if (fptr)
- fptr(fd, param, argc - 1, &args[1]);
- quit = cmd_table[cmdi].quit;
- }
- skip:
-#ifdef USE_READLINE
- free(line);
-#endif
- }
-}
diff --git a/zup.c b/zup.c
index 1647bb3..9dff1c0 100644
--- a/zup.c
+++ b/zup.c
@@ -234,10 +234,19 @@ main(int argc, char *argv[])
int fd = open_tty(param.port, param.baud);
- if (ind == argc || param.repl)
+ if (ind == argc || param.repl) {
repl(fd, &param);
- else
- suc = run_commands(fd, &param, argc - ind, &argv[ind]);
+ } else {
+ argc -= ind;
+ argv = &argv[ind];
+
+ int err = 0;
+ for (int i = 0; i < argc && !err; ++i)
+ err = run_line(fd, &param, argv[i], ":");
+
+ if (err)
+ suc = EXIT_FAILURE;
+ }
close(fd);
exit(EXIT_SUCCESS);
diff --git a/zup.h b/zup.h
index cce84ed..7631e93 100644
--- a/zup.h
+++ b/zup.h
@@ -107,6 +107,9 @@ open_tty(const char *port, int baud);
int
run_commands(int fd, const struct param *param, int ncmd, char **cmds);
+int
+run_line(int fd, struct param *param, char *line, const char *sep);
+
void
repl(int fd, struct param *param);