Add a simple REPL
This commit is contained in:
parent
c7a3e820e0
commit
a3db5aa285
8 changed files with 442 additions and 32 deletions
|
@ -22,6 +22,7 @@ target_sources(vm_lib
|
|||
src/opcode.cpp
|
||||
src/fio.cpp
|
||||
src/stdlib.cpp
|
||||
src/lineedit.cpp
|
||||
|
||||
PUBLIC
|
||||
FILE_SET HEADERS
|
||||
|
@ -41,6 +42,7 @@ target_sources(vm_lib
|
|||
src/opcode.hpp
|
||||
src/fio.hpp
|
||||
src/stdlib.hpp
|
||||
src/lineedit.hpp
|
||||
)
|
||||
|
||||
add_executable(vli src/vli.cpp)
|
||||
|
|
|
@ -204,7 +204,7 @@ class Array : public Object {
|
|||
return Array(TRY(MkGcRoot(pod)));
|
||||
}
|
||||
|
||||
Result<Array> slice(uint64_t start, uint64_t end) {
|
||||
Result<Array> slice(uint64_t start, uint64_t end) const {
|
||||
if (start > end) return ERROR(IndexOutOfRange);
|
||||
uint64_t res_size = end - start;
|
||||
auto pod = TRY(arena_alloc<PodArray>(res_size * sizeof(PodObject*)));
|
||||
|
@ -315,7 +315,7 @@ class ByteArray : public Object {
|
|||
return ByteArray(TRY(MkGcRoot(pod)));
|
||||
}
|
||||
|
||||
Result<ByteArray> slice(uint64_t start, uint64_t end) {
|
||||
Result<ByteArray> slice(uint64_t start, uint64_t end) const {
|
||||
if (start > end) return ERROR(IndexOutOfRange);
|
||||
uint64_t res_size = end - start;
|
||||
auto pod = TRY(arena_alloc<PodByteArray>(res_size * sizeof(char)));
|
||||
|
@ -510,7 +510,7 @@ class String : public Object {
|
|||
return String(TRY(MkGcRoot(pod)));
|
||||
}
|
||||
|
||||
Result<String> slice(uint64_t start, uint64_t end) {
|
||||
Result<String> slice(uint64_t start, uint64_t end) const {
|
||||
if (start > end) return ERROR(IndexOutOfRange);
|
||||
uint64_t res_size = end - start;
|
||||
auto pod = TRY(arena_alloc<PodString>(res_size * sizeof(char32_t)));
|
||||
|
|
|
@ -16,6 +16,7 @@ enum class ErrorCode {
|
|||
CompilationError,
|
||||
ArgumentCountMismatch,
|
||||
IOError,
|
||||
Interrupt,
|
||||
};
|
||||
|
||||
void seterr(const char* err);
|
||||
|
|
394
src/lineedit.cpp
Normal file
394
src/lineedit.cpp
Normal file
|
@ -0,0 +1,394 @@
|
|||
#include "lineedit.hpp"
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "common.hpp"
|
||||
#include "fio.hpp"
|
||||
|
||||
enum class TtyEscape {
|
||||
Null = 0,
|
||||
CtrlA = 1,
|
||||
CtrlB = 2,
|
||||
CtrlC = 3,
|
||||
CtrlD = 4,
|
||||
CtrlE = 5,
|
||||
CtrlF = 6,
|
||||
CtrlH = 8,
|
||||
Tab = 9,
|
||||
CtrlK = 11,
|
||||
CtrlL = 12,
|
||||
Enter = 13,
|
||||
CtrlN = 14,
|
||||
CtrlP = 16,
|
||||
CtrlT = 20,
|
||||
CtrlU = 21,
|
||||
CtrlW = 23,
|
||||
Esc = 27,
|
||||
Backspace = 127,
|
||||
};
|
||||
|
||||
static struct termios orig_termios;
|
||||
static int rawmode = 0;
|
||||
|
||||
template <uint64_t buflen>
|
||||
class CharBuf {
|
||||
public:
|
||||
CharBuf() : _len(0) {}
|
||||
|
||||
Result<void> append(char c) {
|
||||
if (_len + 1 >= buflen) return ERROR(IndexOutOfRange);
|
||||
_buf[_len] = c;
|
||||
_len++;
|
||||
|
||||
return Result<void>();
|
||||
}
|
||||
Result<void> append(const char* s) {
|
||||
uint64_t slen = strlen(s);
|
||||
if (_len + slen >= buflen) return ERROR(IndexOutOfRange);
|
||||
|
||||
memcpy(_buf + _len, s, slen);
|
||||
_len += slen;
|
||||
|
||||
return Result<void>();
|
||||
}
|
||||
|
||||
uint64_t len() { return _len; }
|
||||
const char* buf() { return _buf; }
|
||||
|
||||
private:
|
||||
char _buf[buflen];
|
||||
uint64_t _len;
|
||||
};
|
||||
|
||||
Result<void> enableRawMode(int fd) {
|
||||
struct termios raw;
|
||||
|
||||
if (!isatty(STDIN_FILENO)) return ERROR(IOError);
|
||||
if (tcgetattr(fd, &orig_termios) == -1) return ERROR(IOError);
|
||||
|
||||
raw = orig_termios; /* modify the original mode */
|
||||
/* input modes: no break, no CR to NL, no parity check, no strip char,
|
||||
* no start/stop output control. */
|
||||
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
||||
/* output modes - disable post processing */
|
||||
raw.c_oflag &= ~(OPOST);
|
||||
/* control modes - set 8 bit chars */
|
||||
raw.c_cflag |= (CS8);
|
||||
/* local modes - choing off, canonical off, no extended functions,
|
||||
* no signal chars (^Z,^C) */
|
||||
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
|
||||
/* control chars - set return condition: min number of bytes and timer.
|
||||
* We want read to return every single byte, without timeout. */
|
||||
raw.c_cc[VMIN] = 1;
|
||||
raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
|
||||
|
||||
/* put terminal in raw mode after flushing */
|
||||
if (tcsetattr(fd, TCSAFLUSH, &raw) < 0) return ERROR(IOError);
|
||||
rawmode = 1;
|
||||
|
||||
return Result<void>();
|
||||
}
|
||||
|
||||
void disableRawMode(int fd) {
|
||||
/* Don't even check the return value as it's too late. */
|
||||
if (rawmode && tcsetattr(fd, TCSAFLUSH, &orig_termios) != -1) rawmode = 0;
|
||||
}
|
||||
|
||||
class RawModeGuard {
|
||||
public:
|
||||
RawModeGuard() : fd(-1) {}
|
||||
RawModeGuard(int fd) : fd(fd) {}
|
||||
RawModeGuard(RawModeGuard&& rhs) {
|
||||
fd = rhs.fd;
|
||||
rhs.fd = -1;
|
||||
}
|
||||
RawModeGuard(const RawModeGuard&) = delete;
|
||||
~RawModeGuard() {
|
||||
if (fd >= 0) disableRawMode(fd);
|
||||
}
|
||||
|
||||
static Result<RawModeGuard> create(int fd) {
|
||||
auto res = RawModeGuard(fd);
|
||||
TRY(enableRawMode(fd));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private:
|
||||
int fd;
|
||||
};
|
||||
|
||||
RawModeGuard raw_mode_guard(STDIN_FILENO);
|
||||
|
||||
struct RefreshState {
|
||||
uint64_t num_rows = 0;
|
||||
uint64_t cursor_pos = 0;
|
||||
};
|
||||
|
||||
class LineEdit {
|
||||
public:
|
||||
LineEdit()
|
||||
: prompt(""),
|
||||
cursor_pos(0),
|
||||
line_len(0),
|
||||
term_width(0),
|
||||
input_fd(STDIN_FILENO),
|
||||
output_fd(STDOUT_FILENO) {
|
||||
buf[0] = 0;
|
||||
}
|
||||
|
||||
LineEdit(const char* prompt, int input_fd = STDIN_FILENO,
|
||||
int output_fd = STDOUT_FILENO)
|
||||
: prompt(prompt),
|
||||
cursor_pos(0),
|
||||
line_len(0),
|
||||
term_width(0),
|
||||
input_fd(input_fd),
|
||||
output_fd(output_fd) {
|
||||
buf[0] = 0;
|
||||
}
|
||||
|
||||
LineEdit(const LineEdit& rhs) {
|
||||
prompt = rhs.prompt;
|
||||
memcpy(buf, rhs.buf, buflen);
|
||||
cursor_pos = rhs.cursor_pos;
|
||||
line_len = rhs.line_len;
|
||||
term_width = rhs.term_width;
|
||||
input_fd = rhs.input_fd;
|
||||
output_fd = rhs.output_fd;
|
||||
last_refresh = rhs.last_refresh;
|
||||
}
|
||||
|
||||
static Result<LineEdit> create(const char* prompt) {
|
||||
LineEdit res(prompt);
|
||||
|
||||
TRY(res.init());
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Result<uint64_t> get_term_width() {
|
||||
winsize ws;
|
||||
|
||||
if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
|
||||
/* ioctl() failed. Try to query the terminal itself. */
|
||||
int start, cols;
|
||||
|
||||
/* Get the initial position so we can restore it later. */
|
||||
start = TRY(get_cursor_column());
|
||||
|
||||
/* Go to right margin and get position. */
|
||||
if (write(output_fd, "\x1b[999C", 6) != 6) return ERROR(IOError);
|
||||
cols = TRY(get_cursor_column());
|
||||
|
||||
/* Restore position. */
|
||||
if (cols > start) {
|
||||
char seq[32];
|
||||
snprintf(seq, 32, "\x1b[%dD", cols - start);
|
||||
if (write(output_fd, seq, strlen(seq)) == -1) {
|
||||
return ERROR(IOError);
|
||||
}
|
||||
}
|
||||
return cols;
|
||||
} else {
|
||||
return ws.ws_col;
|
||||
}
|
||||
}
|
||||
|
||||
Result<uint64_t> get_cursor_column() {
|
||||
char buf[32];
|
||||
int cols, rows;
|
||||
unsigned int i = 0;
|
||||
|
||||
/* Report cursor location */
|
||||
if (write(output_fd, "\x1b[6n", 4) != 4) return -1;
|
||||
|
||||
/* Read the response: ESC [ rows ; cols R */
|
||||
while (i < sizeof(buf) - 1) {
|
||||
if (read(input_fd, buf + i, 1) != 1) break;
|
||||
if (buf[i] == 'R') break;
|
||||
i++;
|
||||
}
|
||||
buf[i] = '\0';
|
||||
|
||||
/* Parse it. */
|
||||
if (buf[0] != (char)TtyEscape::Esc || buf[1] != '[') return -1;
|
||||
if (sscanf(buf + 2, "%d;%d", &rows, &cols) != 2) return -1;
|
||||
return cols;
|
||||
}
|
||||
|
||||
Result<String> read_one() {
|
||||
{
|
||||
auto guard = TRY(RawModeGuard::create(input_fd));
|
||||
if (write(output_fd, prompt, strlen(prompt)) == -1) return ERROR(IOError);
|
||||
|
||||
while (TRY(step()));
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
auto res = TRY(String::create(buf));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Result<bool> step() {
|
||||
char c;
|
||||
int nread;
|
||||
char seq[3];
|
||||
|
||||
nread = read(input_fd, &c, 1);
|
||||
if (nread <= 0) return false;
|
||||
|
||||
switch (TtyEscape(c)) {
|
||||
case TtyEscape::Enter:
|
||||
return false;
|
||||
break;
|
||||
case TtyEscape::Backspace:
|
||||
TRY(handle_backspace());
|
||||
break;
|
||||
case TtyEscape::CtrlC:
|
||||
return ERROR(Interrupt);
|
||||
default:
|
||||
TRY(handle_regular_char(c));
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Result<void> handle_regular_char(char c) {
|
||||
if (line_len >= buflen) return Result<void>();
|
||||
|
||||
if (line_len == cursor_pos) {
|
||||
buf[cursor_pos] = c;
|
||||
cursor_pos++;
|
||||
line_len++;
|
||||
buf[cursor_pos] = 0;
|
||||
|
||||
// if (strlen(prompt) + line_len < term_width) {
|
||||
// if (write(output_fd, &c, 1) == -1) return ERROR(IOError);
|
||||
// } else {
|
||||
TRY(refresh_line());
|
||||
//}
|
||||
} else {
|
||||
memmove(buf + cursor_pos + 1, buf + cursor_pos, line_len - cursor_pos);
|
||||
buf[cursor_pos] = c;
|
||||
cursor_pos++;
|
||||
line_len++;
|
||||
buf[line_len] = 0;
|
||||
|
||||
TRY(refresh_line());
|
||||
}
|
||||
return Result<void>();
|
||||
}
|
||||
|
||||
Result<void> handle_backspace() {
|
||||
if (cursor_pos == 0) return Result<void>();
|
||||
memmove(buf + cursor_pos - 1, buf + cursor_pos, line_len - cursor_pos);
|
||||
cursor_pos--;
|
||||
line_len--;
|
||||
buf[line_len] = 0;
|
||||
|
||||
TRY(refresh_line());
|
||||
|
||||
return Result<void>();
|
||||
}
|
||||
|
||||
Result<void> refresh_line() {
|
||||
char seq[64];
|
||||
CharBuf<1024> cbuf;
|
||||
|
||||
RefreshState new_refresh = last_refresh;
|
||||
|
||||
uint64_t prompt_len = strlen(prompt);
|
||||
|
||||
uint64_t num_rows = (prompt_len + line_len + term_width - 1) / term_width;
|
||||
uint64_t cursor_row =
|
||||
(prompt_len + last_refresh.cursor_pos + term_width) / term_width;
|
||||
uint64_t new_relative_row = 0;
|
||||
|
||||
new_refresh.num_rows = num_rows;
|
||||
new_refresh.cursor_pos = cursor_pos;
|
||||
|
||||
// Go to last row
|
||||
if (last_refresh.num_rows - cursor_row > 0) {
|
||||
snprintf(seq, 64, "\x1b[%luB", last_refresh.num_rows - cursor_row);
|
||||
TRY(cbuf.append(seq));
|
||||
}
|
||||
|
||||
// Clear all rows one by one (except the first one)
|
||||
if (last_refresh.num_rows > 1) {
|
||||
for (uint64_t i = 0; i < last_refresh.num_rows - 1; i++) {
|
||||
snprintf(seq, 64, "\r\x1b[0K\x1b[1A");
|
||||
TRY(cbuf.append(seq));
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the first line
|
||||
snprintf(seq, 64, "\r\x1b[0K");
|
||||
TRY(cbuf.append(seq));
|
||||
|
||||
// Write the prompt
|
||||
cbuf.append(prompt);
|
||||
|
||||
// Write the buffer content
|
||||
cbuf.append(buf);
|
||||
|
||||
if (cursor_pos > 0 && cursor_pos == line_len &&
|
||||
(cursor_pos + prompt_len) % term_width == 0) {
|
||||
cbuf.append("\n");
|
||||
cbuf.append("\r");
|
||||
num_rows++;
|
||||
if (num_rows > last_refresh.num_rows) new_refresh.num_rows = num_rows;
|
||||
}
|
||||
|
||||
cursor_row = (prompt_len + cursor_pos + term_width) / term_width;
|
||||
|
||||
// Go up to the cursor row
|
||||
if (num_rows - cursor_row > 0) {
|
||||
snprintf(seq, 64, "\x1b[%luA", num_rows - cursor_row);
|
||||
TRY(cbuf.append(seq));
|
||||
}
|
||||
|
||||
// Go to the cursor column
|
||||
uint64_t col = (prompt_len + cursor_pos) % term_width;
|
||||
if (col > 0) {
|
||||
snprintf(seq, 64, "\r\x1b[%luC", col);
|
||||
TRY(cbuf.append(seq));
|
||||
} else {
|
||||
TRY(cbuf.append("\r"));
|
||||
}
|
||||
|
||||
if (write(output_fd, cbuf.buf(), cbuf.len()) == -1) return ERROR(IOError);
|
||||
|
||||
last_refresh = new_refresh;
|
||||
return Result<void>();
|
||||
}
|
||||
|
||||
private:
|
||||
Result<void> init() {
|
||||
term_width = TRY(get_term_width());
|
||||
|
||||
if (!stdin_isatty()) return ERROR(IOError);
|
||||
return Result<void>();
|
||||
}
|
||||
|
||||
const char* prompt;
|
||||
static const uint64_t buflen = 1024;
|
||||
char buf[buflen + 1];
|
||||
uint64_t cursor_pos;
|
||||
uint64_t line_len;
|
||||
uint64_t term_width;
|
||||
int input_fd;
|
||||
int output_fd;
|
||||
RefreshState last_refresh;
|
||||
};
|
||||
|
||||
Result<String> read_line(const char* prompt) {
|
||||
auto rl = TRY(LineEdit::create(prompt));
|
||||
|
||||
return rl.read_one();
|
||||
}
|
6
src/lineedit.hpp
Normal file
6
src/lineedit.hpp
Normal file
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "result.hpp"
|
||||
|
||||
class String;
|
||||
Result<String> read_line(const char* prompt);
|
|
@ -409,12 +409,12 @@ bool Reader::forward_exponent() {
|
|||
return forward_decimal_number();
|
||||
}
|
||||
|
||||
Result<Value> read_one(Value& value) {
|
||||
Result<Value> read_one(const Value& value) {
|
||||
if (!value.is<String>()) return ERROR(TypeMismatch);
|
||||
auto r = Reader(*value.to<String>());
|
||||
return r.read_one();
|
||||
}
|
||||
Result<Value> read_one(String& value) {
|
||||
Result<Value> read_one(const String& value) {
|
||||
auto r = Reader(value);
|
||||
return r.read_one();
|
||||
}
|
||||
|
@ -424,7 +424,7 @@ Result<Value> read_one(const char* value) {
|
|||
return r.read_one();
|
||||
}
|
||||
|
||||
Result<Value> read_multiple(String& value) {
|
||||
Result<Value> read_multiple(const String& value) {
|
||||
auto r = Reader(value);
|
||||
return r.read_multiple();
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
class Reader {
|
||||
public:
|
||||
Reader(String& str) : _str(str) {}
|
||||
Reader(const String& str) : _str(str) {}
|
||||
|
||||
Result<Value> read_one();
|
||||
Result<Value> read_multiple();
|
||||
|
@ -38,12 +38,12 @@ class Reader {
|
|||
bool match(const char* str);
|
||||
bool match(char c);
|
||||
|
||||
String& _str;
|
||||
const String& _str;
|
||||
SourcePosition position_{1, 1, 0};
|
||||
};
|
||||
|
||||
Result<Value> read_one(Value& value);
|
||||
Result<Value> read_one(String& value);
|
||||
Result<Value> read_one(const Value& value);
|
||||
Result<Value> read_one(const String& value);
|
||||
Result<Value> read_one(const char* value);
|
||||
|
||||
Result<Value> read_multiple(String& value);
|
||||
Result<Value> read_multiple(const String& value);
|
||||
|
|
49
src/vli.cpp
49
src/vli.cpp
|
@ -5,44 +5,51 @@
|
|||
#include "compiler.hpp"
|
||||
#include "die.hpp"
|
||||
#include "fio.hpp"
|
||||
#include "lineedit.hpp"
|
||||
#include "reader.hpp"
|
||||
#include "vm.hpp"
|
||||
#include "writer.hpp"
|
||||
|
||||
StaticArena<64 * 1024 * 1024> arena;
|
||||
|
||||
Result<void> run(int argc, const char* argv[]) {
|
||||
String src = DIEX(String::create(""));
|
||||
if (argc == 1) {
|
||||
if (stdin_isatty()) {
|
||||
die("Code expected at stdin, not a tty.\n");
|
||||
}
|
||||
src = TRY(read_stdin());
|
||||
} else {
|
||||
src = TRY(read_file(argv[1]));
|
||||
}
|
||||
|
||||
// auto code_str = TRY(String::create("(* (+ 1 2 3) (/ 4 2))"));
|
||||
// auto code_str =
|
||||
// TRY(String::create("((lambda (f y) (f y)) (lambda (x) (* x x)) 2)"));
|
||||
|
||||
Result<Value> run_string(const String& src) {
|
||||
auto parsed = TRY(read_multiple(src));
|
||||
// auto code_str_written = TRY(write_multiple(parsed));
|
||||
|
||||
TRY(arena_gc());
|
||||
|
||||
// TRY(debug_print(code_str_written));
|
||||
|
||||
auto compiled = TRY(compile(parsed));
|
||||
Module& mod = *compiled.to<Module>();
|
||||
|
||||
// TRY(debug_print(TRY(mod.globals())));
|
||||
|
||||
auto vm = TRY(VM::create());
|
||||
|
||||
auto res = TRY(vm.run(mod));
|
||||
|
||||
// TRY(debug_print(res));
|
||||
return res;
|
||||
}
|
||||
|
||||
Result<void> run_repl() {
|
||||
while (true) {
|
||||
auto src = TRY(read_line("vli> "));
|
||||
debug_print(src);
|
||||
auto res = TRY(run_string(src));
|
||||
debug_print(res);
|
||||
}
|
||||
|
||||
return Result<void>();
|
||||
}
|
||||
|
||||
Result<void> run(int argc, const char* argv[]) {
|
||||
String src = DIEX(String::create(""));
|
||||
if (argc == 1) {
|
||||
if (stdin_isatty()) {
|
||||
return run_repl();
|
||||
}
|
||||
src = TRY(read_stdin());
|
||||
TRY(run_string(src));
|
||||
} else {
|
||||
src = TRY(read_file(argv[1]));
|
||||
TRY(run_string(src));
|
||||
}
|
||||
|
||||
return Result<void>();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue