Skip to content
This repository was archived by the owner on Oct 12, 2026. It is now read-only.

Commit 4064381

Browse files
committed
Improved REPL
1 parent f6ea114 commit 4064381

4 files changed

Lines changed: 166 additions & 70 deletions

File tree

‎include/lbd/intp/interpreter.h‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ namespace intp::interp {
1818
std::shared_ptr<Env> env; /// Environment at the time of Lambda Expression creation
1919

2020
friend std::ostream &operator<<(std::ostream &os, const Closure &closure);
21+
22+
[[nodiscard]] std::string to_string() const;
2123
};
2224

2325
using Value = std::variant<double, std::string, Closure, NativeFunction>;
@@ -31,9 +33,13 @@ namespace intp::interp {
3133
Impl impl;
3234

3335
friend std::ostream &operator<<(std::ostream &os, const NativeFunction &native_fn);
36+
37+
[[nodiscard]] std::string to_string() const;
3438
};
3539

3640
/// Pretty print a runtime value for REPL/diagnostics
41+
std::string val_to_string(const Value &value);
42+
3743
std::ostream &operator<<(std::ostream &os, const Value &value);
3844

3945
/// Lazy Thunk (call-by-need)
@@ -71,7 +77,7 @@ namespace intp::interp {
7177

7278
void bind(const std::string &name, std::shared_ptr<Thunk> thunk);
7379

74-
void dump(std::ostream &os, bool force) const;
80+
std::vector<std::vector<std::string> > to_vector(bool force) const;
7581
};
7682

7783
Value eval_expr(const fe::ast::Expression &expr, std::shared_ptr<Env> env);

‎include/lbd/term.h‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#pragma once
2+
3+
#include <iostream>
4+
#include <vector>
5+
6+
#if defined(_WIN32)
7+
#include <windows.h>
8+
#endif
9+
10+
inline bool enable_virtual_terminal() {
11+
#if defined(_WIN32)
12+
const HANDLE output_handle = GetStdHandle(STD_OUTPUT_HANDLE); // NOLINT(*-misplaced-const)
13+
if (output_handle == INVALID_HANDLE_VALUE) {
14+
return false;
15+
}
16+
DWORD dwMode = 0;
17+
if (!GetConsoleMode(output_handle, &dwMode)) {
18+
return false;
19+
}
20+
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
21+
if (!SetConsoleMode(output_handle, dwMode)) {
22+
return false;
23+
}
24+
#endif
25+
return true;
26+
}
27+
28+
namespace colors {
29+
constexpr auto RESET = "\033[0m";
30+
constexpr auto RED = "\033[31m";
31+
constexpr auto GREEN = "\033[32m";
32+
constexpr auto YELLOW = "\033[33m";
33+
constexpr auto BLUE = "\033[34m";
34+
constexpr auto CYAN = "\033[36m";
35+
constexpr auto WHITE = "\033[37m";
36+
constexpr auto GREY = "\033[38;5;200m";
37+
}
38+
39+
inline void print_table_row(const std::vector<std::string> &row, const std::vector<size_t> &widths,
40+
const std::string &color = "") {
41+
std::cout << color;
42+
for (size_t i = 0; i < row.size(); ++i) {
43+
std::cout << std::setw(static_cast<int>(widths[i])) << std::left << row[i] << " ";
44+
}
45+
std::cout << colors::RESET << std::endl;
46+
}
47+
48+
inline void print_table(const std::vector<std::string> &headers, const std::vector<std::vector<std::string> > &data,
49+
const std::string &color = colors::CYAN) {
50+
std::vector<size_t> widths(headers.size(), 0);
51+
for (size_t i = 0; i < headers.size(); ++i) {
52+
widths[i] = headers[i].size();
53+
}
54+
for (const auto &row: data) {
55+
for (size_t i = 0; i < row.size(); ++i) {
56+
if (static_cast<int>(row[i].size()) > widths[i]) {
57+
widths[i] = row[i].size();
58+
}
59+
}
60+
}
61+
print_table_row(headers, widths, color);
62+
for (size_t i = 0; i < headers.size(); ++i) {
63+
std::cout << std::string(widths[i], '-') << " ";
64+
}
65+
std::cout << std::endl;
66+
for (const auto &row: data) {
67+
print_table_row(row, widths);
68+
}
69+
}

‎src/intp/interpreter.cpp‎

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,27 @@
11
#include <lbd/intp/interpreter.h>
22
#include <lbd/intp/builtins.h>
33
#include <lbd/error.h>
4+
#include <sstream>
45

56
namespace intp::interp {
7+
[[nodiscard]] std::string Closure::to_string() const {
8+
std::ostringstream oss;
9+
oss << "<closure: " << param << ">";
10+
return oss.str();
11+
}
12+
613
std::ostream &operator<<(std::ostream &os, const Closure &closure) {
7-
return os << "<closure: " << closure.param << ">";
14+
return os << closure.to_string();
15+
}
16+
17+
[[nodiscard]] std::string NativeFunction::to_string() const {
18+
std::ostringstream oss;
19+
oss << "<native_fn: " << name << " " << arity << ">";
20+
return oss.str();
821
}
922

1023
std::ostream &operator<<(std::ostream &os, const NativeFunction &native_fn) {
11-
return os << "<native_fn: " << native_fn.name << " " << native_fn.arity << ">";
24+
return os << native_fn.to_string();
1225
}
1326

1427
static std::ostream &print_loc_prefix(std::ostream &os, const std::optional<fe::loc::Loc> &loc) {
@@ -18,6 +31,22 @@ namespace intp::interp {
1831
return os;
1932
}
2033

34+
std::string val_to_string(const Value &value) {
35+
return std::visit([&]<typename T0>(T0 &&arg) {
36+
using T = std::decay_t<T0>;
37+
if constexpr (std::is_same_v<T, double>) {
38+
return std::to_string(arg);
39+
} else if constexpr (std::is_same_v<T, std::string>) {
40+
return arg;
41+
} else if constexpr (std::is_same_v<T, Closure> ||
42+
std::is_same_v<T, NativeFunction>) {
43+
return arg.to_string();
44+
} else {
45+
STATIC_ASSERT_UNREACHABLE_T(T, "unhandled runtime value");
46+
}
47+
}, value);
48+
}
49+
2150
std::ostream &operator<<(std::ostream &os, const Value &value) {
2251
std::visit([&]<typename T0>(T0 &&arg) {
2352
using T = std::decay_t<T0>;
@@ -91,29 +120,29 @@ namespace intp::interp {
91120
table[name] = std::move(thunk);
92121
}
93122

94-
void Env::dump(std::ostream &os, const bool force) const {
123+
std::vector<std::vector<std::string> > Env::to_vector(const bool force) const {
124+
std::vector<std::vector<std::string> > vec;
125+
vec.reserve(table.size());
95126
for (const auto &[name, thunk]: table) {
96-
os << " " << name << " = ";
127+
std::string val_str = "<thunk: unevaluated>";
97128
try {
98129
if (force) {
99130
const Value &val = thunk->force();
100-
os << val;
101-
} else {
102-
if (thunk->cached) {
103-
os << *(thunk->cached); // already computed
104-
} else {
105-
os << "<thunk: unevaluated>";
106-
}
131+
val_str = val_to_string(val);
132+
} else if (thunk->cached) {
133+
val_str = val_to_string(*thunk->cached); // already computed
107134
}
108-
} catch (const std::exception &ex) {
109-
os << "<thunk: unevaluated>";
135+
} catch (const std::exception &) {
136+
val_str = "<thunk: error>";
110137
}
111-
os << std::endl;
138+
vec.push_back({name, val_str});
112139
}
113140
if (parent) {
114-
os << "info: parent environment" << std::endl;
115-
parent->dump(os, force);
141+
auto parent_vec = parent->to_vector(force);
142+
vec.insert(vec.end(), std::make_move_iterator(parent_vec.begin()),
143+
std::make_move_iterator(parent_vec.end()));
116144
}
145+
return vec;
117146
}
118147

119148
static Value eval_iden_ast_node(const fe::ast::IdenAstNode &iden_ast_node, const std::shared_ptr<Env> &env) {

‎src/repl.cpp‎

Lines changed: 45 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,21 @@
22
#include <fstream>
33
#include <iostream>
44
#include <lbd/repl.h>
5+
#include <lbd/term.h>
56
#include <lbd/fe/lexer.h>
67
#include <lbd/fe/parser.h>
78
#include <lbd/intp/interpreter.h>
89

9-
namespace repl {
10-
static bool force_on_env_dump = false;
11-
static bool debug_enabled = false;
12-
13-
static std::optional<std::string> read_file(const std::string &filepath) {
14-
std::ifstream file(filepath);
15-
if (!file) {
16-
// Could not open file
17-
return std::nullopt;
18-
}
19-
return std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
20-
}
10+
// TODO: Add support for clicking enter during lambda expression definition
2111

12+
namespace repl {
2213
static void process_load_command(const std::string &arg,
2314
std::optional<std::shared_ptr<intp::interp::Env> > &shared_env) {
2415
try {
25-
const std::string filepath = arg;
16+
const std::string &filepath = arg;
2617
if (!std::filesystem::exists(filepath)) {
27-
std::cerr << "error: filepath "
28-
<< filepath << " does not exist"
29-
<< std::endl;
18+
std::cerr << colors::RED << "error: filepath " << filepath << " does not exist"
19+
<< colors::RESET << std::endl;
3020
return;
3121
}
3222
fe::lexer::Lexer lexer(filepath, fe::lexer::FromFile{});
@@ -46,45 +36,29 @@ namespace repl {
4636
}
4737
}
4838
}
49-
std::cout << "info: file loaded: " << filepath << std::endl;
39+
std::cout << colors::BLUE << "info: file loaded: " << filepath << colors::RESET << std::endl;
5040
} catch (const std::exception &ex) {
51-
std::cerr << "error: could not load file: " << ex.what() << std::endl;
41+
std::cerr << colors::RED << "error: could not load file: " << ex.what() << colors::RESET << std::endl;
5242
}
5343
}
5444

55-
static void help() {
56-
std::cout
57-
<< "available REPL commands:\n"
58-
<< " general:\n"
59-
<< " :q, :quit, :exit Exit the REPL\n"
60-
<< " :c, :clear, :cls Clear the screen\n"
61-
<< " :h, :help, :? Show this help message\n"
62-
<< " :l <filepath>, :load <filepath> Load file into REPL\n"
63-
<< " :r, :reload Reset environment (forget all bindings)\n"
64-
<< " :d, :debug Toggle debug mode\n"
65-
<< " inspection:\n"
66-
<< " :e, :env Show all variables in current environment\n"
67-
<< " :force Toggle forcing evaluation when dumping env\n"
68-
<< "current options:\n"
69-
<< " debug [" << (debug_enabled ? "on " : "off") <<
70-
"] (use :debug to toggle)\n"
71-
<< " force-on-env-dump [" << (force_on_env_dump ? "on " : "off") <<
72-
"] (use :force to toggle)"
73-
<< std::endl;
74-
}
45+
#define on_off(val) ((val) ? "on " : "off")
7546

7647
void loop(const bool debug) {
77-
debug_enabled = debug;
78-
std::cout << "lambda-discipline REPL.\ntype :quit to exit." << std::endl;
48+
enable_virtual_terminal();
49+
static bool force_on_env_dump = false;
50+
static bool debug_enabled = false;
51+
std::cout << colors::BLUE << "Welcome to lambda-discipline REPL.\nType :quit to exit."
52+
<< colors::RESET << std::endl;
7953
std::string line;
8054
std::optional<std::shared_ptr<intp::interp::Env> > shared_global_env = std::nullopt;
8155
while (true) {
82-
std::cout << "lbd> ";
56+
std::cout << colors::CYAN << "\n>> " << colors::RESET;
8357
if (!std::getline(std::cin, line)) {
8458
break; // EOF (C-d / C-z)
8559
}
8660
if (line == ":q" || line == ":quit" || line == ":exit") {
87-
std::cout << "exiting REPL" << std::endl;
61+
std::cout << colors::BLUE << "exiting REPL" << colors::RESET << std::endl;
8862
break;
8963
}
9064
if (line == ":c" || line == ":clear" || line == ":cls") {
@@ -96,24 +70,41 @@ namespace repl {
9670
continue;
9771
}
9872
if (line == ":h" || line == ":help" || line == ":?") {
99-
help();
73+
std::cout << std::endl;
74+
print_table({"General Commands", "Argument", "Description"}, {
75+
{":q, :quit, :exit", "", "Clear the screen"},
76+
{":c, :clear, :cls", "", "Exit the REPL"},
77+
{":h, :help, :?", "", "Display this help message"},
78+
{":l, :load", "<filepath>", "Load file into REPL"},
79+
{":r, :reset", "", "Reset environment"},
80+
{":d, :debug", "", "Toogle debug mode"}
81+
}, colors::GREEN);
82+
std::cout << std::endl;
83+
print_table({"Inspection Commands", "Argument", "Description"}, {
84+
{":e, :env", "", "Dump environment bindings"},
85+
{":force", "", "Force thunk evaluation on dump"}
86+
}, colors::GREEN);
87+
std::cout << std::endl;
88+
print_table({"Options", "State", "Help"}, {
89+
{"debug", on_off(debug_enabled), "use :debug to toggle"},
90+
{"force-on-env-dump", on_off(force_on_env_dump), "use :force to toggle"}
91+
}, colors::GREEN);
10092
continue;
10193
}
10294
if (line == ":env" || line == ":e") {
95+
std::cout << std::endl;
10396
if (shared_global_env) {
104-
std::cout << "info: environment bindings:" << std::endl;
105-
(*shared_global_env)->dump(std::cout, force_on_env_dump);
97+
print_table({"Symbol", "Thunk"}, (*shared_global_env)->to_vector(force_on_env_dump), colors::GREEN);
10698
} else {
107-
std::cout << "info: environment is empty" << std::endl;
99+
print_table({"Symbol", "Thunk"}, {{"Empty"}}, colors::GREEN);
108100
}
109101
continue;
110102
}
111-
if (line == ":reload" || line == ":r") {
103+
if (line == ":reset" || line == ":r") {
112104
shared_global_env.reset();
113-
std::cout << "info: environment reloaded" << std::endl;
114105
continue;
115106
}
116-
if (line == ":debug") {
107+
if (line == ":debug" || line == ":d") {
117108
debug_enabled = !debug_enabled;
118109
continue;
119110
}
@@ -139,21 +130,22 @@ namespace repl {
139130
const std::vector<fe::token::Token> tokens = lexer.lex_all();
140131
if (debug) {
141132
for (const auto &tok: tokens) {
142-
std::cout << tok << "\n";
133+
std::cout << colors::YELLOW << tok << colors::RESET << std::endl;
143134
}
144135
}
145136
// Parse
146137
fe::parser::Parser parser(tokens);
147138
if (debug) {
148-
std::cout << parser.program << "\n";
139+
std::cout << colors::YELLOW << parser.program << colors::RESET << std::endl;
149140
}
150141
// Interpret
142+
// TODO: Add flag to disable printing in REPL (disabled by default)
151143
const auto [global_env, value] = intp::interp::interpret(
152144
parser.program, shared_global_env, {.own_expr = true});
153-
std::cout << "===> " << value << "\n";
145+
std::cout << colors::GREEN << "=> " << value << colors::RESET << std::endl;
154146
shared_global_env = global_env;
155147
} catch (const std::exception &ex) {
156-
std::cerr << "error: " << ex.what() << "\n";
148+
std::cerr << colors::RED << "error: " << ex.what() << colors::RESET << std::endl;
157149
}
158150
}
159151
}

0 commit comments

Comments
 (0)