193 lines
4.7 KiB
C
193 lines
4.7 KiB
C
|
|
#define _POSIX_C_SOURCE 200809L
|
||
|
|
#include "repl.h"
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <string.h>
|
||
|
|
#include <ctype.h>
|
||
|
|
#include <signal.h>
|
||
|
|
#include <unistd.h>
|
||
|
|
#include <readline/readline.h>
|
||
|
|
#include <readline/history.h>
|
||
|
|
|
||
|
|
static volatile bool g_interrupted = false;
|
||
|
|
|
||
|
|
static void sigint_handler(int sig) {
|
||
|
|
(void)sig;
|
||
|
|
g_interrupted = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
RavaREPL_t* rava_repl_create(void) {
|
||
|
|
RavaREPL_t *repl = calloc(1, sizeof(RavaREPL_t));
|
||
|
|
if (!repl) return NULL;
|
||
|
|
|
||
|
|
repl->session = rava_repl_session_create();
|
||
|
|
if (!repl->session) {
|
||
|
|
free(repl);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
repl->input = rava_repl_input_create();
|
||
|
|
if (!repl->input) {
|
||
|
|
rava_repl_session_destroy(repl->session);
|
||
|
|
free(repl);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
repl->executor = rava_repl_executor_create(repl->session);
|
||
|
|
if (!repl->executor) {
|
||
|
|
rava_repl_input_destroy(repl->input);
|
||
|
|
rava_repl_session_destroy(repl->session);
|
||
|
|
free(repl);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
repl->running = true;
|
||
|
|
repl->interactive = true;
|
||
|
|
|
||
|
|
return repl;
|
||
|
|
}
|
||
|
|
|
||
|
|
void rava_repl_destroy(RavaREPL_t *repl) {
|
||
|
|
if (!repl) return;
|
||
|
|
rava_repl_executor_destroy(repl->executor);
|
||
|
|
rava_repl_input_destroy(repl->input);
|
||
|
|
rava_repl_session_destroy(repl->session);
|
||
|
|
free(repl);
|
||
|
|
}
|
||
|
|
|
||
|
|
void rava_repl_print_banner(void) {
|
||
|
|
printf("Rava %s Interactive Interpreter\n", RAVA_REPL_VERSION);
|
||
|
|
printf("Type \"%%help\" for commands, \"%%quit\" to exit\n\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
void rava_repl_print_prompt(RavaREPL_t *repl, bool continuation) {
|
||
|
|
(void)repl;
|
||
|
|
printf("%s", continuation ? "... " : ">>> ");
|
||
|
|
fflush(stdout);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool rava_repl_process_line(RavaREPL_t *repl, const char *line) {
|
||
|
|
if (!repl || !line) return true;
|
||
|
|
|
||
|
|
while (*line && isspace(*line)) line++;
|
||
|
|
if (strlen(line) == 0) return true;
|
||
|
|
|
||
|
|
if (rava_repl_command_is_command(line)) {
|
||
|
|
char *arg = NULL;
|
||
|
|
RavaREPLCommand_e cmd = rava_repl_command_parse(line, &arg);
|
||
|
|
|
||
|
|
if (cmd == RAVA_CMD_QUIT) {
|
||
|
|
free(arg);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
rava_repl_history_add(repl->session->history, line);
|
||
|
|
bool result = rava_repl_command_execute(repl->session, cmd, arg);
|
||
|
|
free(arg);
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
RavaREPLInputState_e state = rava_repl_input_append(repl->input, line);
|
||
|
|
|
||
|
|
if (state == RAVA_REPL_INPUT_COMPLETE) {
|
||
|
|
const char *code = rava_repl_input_get(repl->input);
|
||
|
|
rava_repl_history_add(repl->session->history, code);
|
||
|
|
rava_repl_executor_execute(repl->executor, code);
|
||
|
|
rava_repl_input_reset(repl->input);
|
||
|
|
} else if (state == RAVA_REPL_INPUT_ERROR) {
|
||
|
|
rava_repl_output_error("Input error", "Buffer overflow or invalid input");
|
||
|
|
rava_repl_input_reset(repl->input);
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
static char* read_line_stdin(const char *prompt) {
|
||
|
|
printf("%s", prompt);
|
||
|
|
fflush(stdout);
|
||
|
|
|
||
|
|
char *line = NULL;
|
||
|
|
size_t len = 0;
|
||
|
|
ssize_t read = getline(&line, &len, stdin);
|
||
|
|
|
||
|
|
if (read == -1) {
|
||
|
|
free(line);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (read > 0 && line[read - 1] == '\n') {
|
||
|
|
line[read - 1] = '\0';
|
||
|
|
}
|
||
|
|
|
||
|
|
return line;
|
||
|
|
}
|
||
|
|
|
||
|
|
int rava_repl_run(void) {
|
||
|
|
struct sigaction sa;
|
||
|
|
sa.sa_handler = sigint_handler;
|
||
|
|
sigemptyset(&sa.sa_mask);
|
||
|
|
sa.sa_flags = 0;
|
||
|
|
sigaction(SIGINT, &sa, NULL);
|
||
|
|
|
||
|
|
RavaREPL_t *repl = rava_repl_create();
|
||
|
|
if (!repl) {
|
||
|
|
fprintf(stderr, "Failed to create REPL\n");
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool is_tty = isatty(fileno(stdin));
|
||
|
|
|
||
|
|
rava_repl_print_banner();
|
||
|
|
|
||
|
|
char history_file[256];
|
||
|
|
const char *home = getenv("HOME");
|
||
|
|
if (home && is_tty) {
|
||
|
|
snprintf(history_file, sizeof(history_file), "%s/.rava_history", home);
|
||
|
|
rava_repl_history_load(repl->session->history, history_file);
|
||
|
|
read_history(history_file);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (is_tty) {
|
||
|
|
rl_bind_key('\t', rl_complete);
|
||
|
|
}
|
||
|
|
|
||
|
|
while (repl->running) {
|
||
|
|
bool continuation = !rava_repl_input_is_empty(repl->input);
|
||
|
|
const char *prompt = continuation ? "... " : ">>> ";
|
||
|
|
|
||
|
|
char *line;
|
||
|
|
if (is_tty) {
|
||
|
|
line = readline(prompt);
|
||
|
|
} else {
|
||
|
|
line = read_line_stdin(prompt);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!line) {
|
||
|
|
if (is_tty) printf("\n");
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (strlen(line) == 0) {
|
||
|
|
free(line);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (is_tty && strlen(line) > 0) {
|
||
|
|
add_history(line);
|
||
|
|
}
|
||
|
|
|
||
|
|
g_interrupted = false;
|
||
|
|
repl->running = rava_repl_process_line(repl, line);
|
||
|
|
|
||
|
|
free(line);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (home && is_tty) {
|
||
|
|
rava_repl_history_save(repl->session->history, history_file);
|
||
|
|
write_history(history_file);
|
||
|
|
}
|
||
|
|
|
||
|
|
rava_repl_destroy(repl);
|
||
|
|
return 0;
|
||
|
|
}
|