Add a simple REPL

This commit is contained in:
Konstantin Nazarov 2024-08-26 13:16:05 +01:00
parent c7a3e820e0
commit a3db5aa285
Signed by: knazarov
GPG key ID: 4CFE0A42FA409C22
8 changed files with 442 additions and 32 deletions

View file

@ -22,6 +22,7 @@ target_sources(vm_lib
src/opcode.cpp src/opcode.cpp
src/fio.cpp src/fio.cpp
src/stdlib.cpp src/stdlib.cpp
src/lineedit.cpp
PUBLIC PUBLIC
FILE_SET HEADERS FILE_SET HEADERS
@ -41,6 +42,7 @@ target_sources(vm_lib
src/opcode.hpp src/opcode.hpp
src/fio.hpp src/fio.hpp
src/stdlib.hpp src/stdlib.hpp
src/lineedit.hpp
) )
add_executable(vli src/vli.cpp) add_executable(vli src/vli.cpp)

View file

@ -204,7 +204,7 @@ class Array : public Object {
return Array(TRY(MkGcRoot(pod))); 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); if (start > end) return ERROR(IndexOutOfRange);
uint64_t res_size = end - start; uint64_t res_size = end - start;
auto pod = TRY(arena_alloc<PodArray>(res_size * sizeof(PodObject*))); auto pod = TRY(arena_alloc<PodArray>(res_size * sizeof(PodObject*)));
@ -315,7 +315,7 @@ class ByteArray : public Object {
return ByteArray(TRY(MkGcRoot(pod))); 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); if (start > end) return ERROR(IndexOutOfRange);
uint64_t res_size = end - start; uint64_t res_size = end - start;
auto pod = TRY(arena_alloc<PodByteArray>(res_size * sizeof(char))); auto pod = TRY(arena_alloc<PodByteArray>(res_size * sizeof(char)));
@ -510,7 +510,7 @@ class String : public Object {
return String(TRY(MkGcRoot(pod))); 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); if (start > end) return ERROR(IndexOutOfRange);
uint64_t res_size = end - start; uint64_t res_size = end - start;
auto pod = TRY(arena_alloc<PodString>(res_size * sizeof(char32_t))); auto pod = TRY(arena_alloc<PodString>(res_size * sizeof(char32_t)));

View file

@ -16,6 +16,7 @@ enum class ErrorCode {
CompilationError, CompilationError,
ArgumentCountMismatch, ArgumentCountMismatch,
IOError, IOError,
Interrupt,
}; };
void seterr(const char* err); void seterr(const char* err);

394
src/lineedit.cpp Normal file
View 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
View file

@ -0,0 +1,6 @@
#pragma once
#include "result.hpp"
class String;
Result<String> read_line(const char* prompt);

View file

@ -409,12 +409,12 @@ bool Reader::forward_exponent() {
return forward_decimal_number(); return forward_decimal_number();
} }
Result<Value> read_one(Value& value) { Result<Value> read_one(const Value& value) {
if (!value.is<String>()) return ERROR(TypeMismatch); if (!value.is<String>()) return ERROR(TypeMismatch);
auto r = Reader(*value.to<String>()); auto r = Reader(*value.to<String>());
return r.read_one(); return r.read_one();
} }
Result<Value> read_one(String& value) { Result<Value> read_one(const String& value) {
auto r = Reader(value); auto r = Reader(value);
return r.read_one(); return r.read_one();
} }
@ -424,7 +424,7 @@ Result<Value> read_one(const char* value) {
return r.read_one(); return r.read_one();
} }
Result<Value> read_multiple(String& value) { Result<Value> read_multiple(const String& value) {
auto r = Reader(value); auto r = Reader(value);
return r.read_multiple(); return r.read_multiple();
} }

View file

@ -5,7 +5,7 @@
class Reader { class Reader {
public: public:
Reader(String& str) : _str(str) {} Reader(const String& str) : _str(str) {}
Result<Value> read_one(); Result<Value> read_one();
Result<Value> read_multiple(); Result<Value> read_multiple();
@ -38,12 +38,12 @@ class Reader {
bool match(const char* str); bool match(const char* str);
bool match(char c); bool match(char c);
String& _str; const String& _str;
SourcePosition position_{1, 1, 0}; SourcePosition position_{1, 1, 0};
}; };
Result<Value> read_one(Value& value); Result<Value> read_one(const Value& value);
Result<Value> read_one(String& value); Result<Value> read_one(const String& value);
Result<Value> read_one(const char* value); Result<Value> read_one(const char* value);
Result<Value> read_multiple(String& value); Result<Value> read_multiple(const String& value);

View file

@ -5,44 +5,51 @@
#include "compiler.hpp" #include "compiler.hpp"
#include "die.hpp" #include "die.hpp"
#include "fio.hpp" #include "fio.hpp"
#include "lineedit.hpp"
#include "reader.hpp" #include "reader.hpp"
#include "vm.hpp" #include "vm.hpp"
#include "writer.hpp" #include "writer.hpp"
StaticArena<64 * 1024 * 1024> arena; StaticArena<64 * 1024 * 1024> arena;
Result<void> run(int argc, const char* argv[]) { Result<Value> run_string(const String& src) {
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)"));
auto parsed = TRY(read_multiple(src)); auto parsed = TRY(read_multiple(src));
// auto code_str_written = TRY(write_multiple(parsed));
TRY(arena_gc()); TRY(arena_gc());
// TRY(debug_print(code_str_written));
auto compiled = TRY(compile(parsed)); auto compiled = TRY(compile(parsed));
Module& mod = *compiled.to<Module>(); Module& mod = *compiled.to<Module>();
// TRY(debug_print(TRY(mod.globals())));
auto vm = TRY(VM::create()); auto vm = TRY(VM::create());
auto res = TRY(vm.run(mod)); 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>(); return Result<void>();
} }