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

Commit 4bf29cd

Browse files
committed
feat: add support for multi-line expression in REPL
1 parent 078d75f commit 4bf29cd

1 file changed

Lines changed: 122 additions & 66 deletions

File tree

‎src/repl.cpp‎

Lines changed: 122 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
#include <lbd/intp/interpreter.h>
99
#include <lbd/exceptions.h>
1010

11-
// TODO: Add support for clicking enter during lambda expression definition
12-
1311
#define on_off(val) ((val) ? "on " : "off")
1412

1513
namespace repl {
@@ -46,91 +44,149 @@ namespace repl {
4644
options_v.logger.info("info: file loaded ", filepath);
4745
}
4846

47+
static int compute_paren_depth(const std::string &s) {
48+
int depth = 0;
49+
for (const char c: s) {
50+
if (c == '(') depth++;
51+
else if (c == ')') depth = std::max(0, depth - 1);
52+
}
53+
return depth;
54+
}
55+
56+
static std::string trim(const std::string &s) {
57+
size_t start = 0;
58+
while (start < s.size() && std::isspace(static_cast<unsigned char>(s[start]))) start++;
59+
size_t end = s.size();
60+
while (end > start && std::isspace(static_cast<unsigned char>(s[end - 1]))) end--;
61+
return s.substr(start, end - start);
62+
}
4963

5064
void loop(const bool debug) {
5165
enable_virtual_terminal();
5266

5367
static logs::Logger logger(false, true, false);
5468
options_v = {.own_expr = true, .force_on_env_dump = false, .debug = debug, .logger = logger};
5569

56-
std::string line;
70+
std::string line, buffer;
71+
size_t indent_level = 0;
5772
std::optional<std::shared_ptr<intp::interp::Env> > shared_global_env = std::nullopt;
5873

5974
options_v.logger.info("Welcome to lambda-discipline REPL.\nType :quit to exit.");
6075

6176
while (true) {
6277
try {
63-
std::cout << colors::CYAN << "\n>> " << colors::RESET;
64-
if (!std::getline(std::cin, line)) {
65-
break; // EOF (C-d / C-z)
66-
}
67-
if (line == ":q" || line == ":quit" || line == ":exit") {
68-
options_v.logger.info("\nexiting REPL.");
69-
break;
78+
// Prompt depends on whether we are continuing a buffer
79+
if (buffer.empty()) {
80+
std::cout << colors::CYAN << "\n>> " << colors::RESET;
81+
} else {
82+
std::cout << colors::CYAN << ".. " << colors::RESET;
7083
}
71-
if (line == ":c" || line == ":clear" || line == ":cls") {
84+
for (size_t i = 0; i < indent_level; i++) std::cout << " ";
85+
86+
if (!std::getline(std::cin, line)) break; // EOF (C-d / C-z)
87+
88+
// REPL arguments parsing
89+
{
90+
if (line == ":q" || line == ":quit" || line == ":exit") {
91+
options_v.logger.info("\nexiting REPL.");
92+
break;
93+
}
94+
95+
if (line == ":c" || line == ":clear" || line == ":cls") {
7296
#ifdef _WIN32
73-
std::system("cls");
97+
std::system("cls");
7498
#else
75-
std::system("clear");
99+
std::system("clear");
76100
#endif
77-
continue;
78-
}
79-
if (line == ":h" || line == ":help" || line == ":?") {
80-
std::cout << std::endl;
81-
print_table({"General Commands", "Argument", "Description"}, {
82-
{":q, :quit, :exit", "", "Clear the screen"},
83-
{":c, :clear, :cls", "", "Exit the REPL"},
84-
{":h, :help, :?", "", "Display this help message"},
85-
{":l, :load", "<filepath>", "Load file into REPL"},
86-
{":r, :reset", "", "Reset environment"},
87-
{":d, :debug", "", "Toggle debug mode"}
88-
}, colors::GREEN);
89-
std::cout << std::endl;
90-
print_table({"Inspection Commands", "Argument", "Description"}, {
91-
{":e, :env", "", "Dump environment bindings"},
92-
{":force", "", "Force thunk evaluation on dump"}
93-
}, colors::GREEN);
94-
std::cout << std::endl;
95-
print_table({"Options", "State", "Help"}, {
96-
{"debug", on_off(options_v.debug), "use :debug to toggle"},
97-
{"force-on-env-dump", on_off(options_v.force_on_env_dump), "use :force to toggle"}
98-
}, colors::GREEN);
99-
continue;
100-
}
101-
if (line == ":env" || line == ":e") {
102-
std::cout << std::endl;
103-
if (shared_global_env) {
104-
print_table({"Symbol", "Thunk"}, (*shared_global_env)->to_vector(options_v.force_on_env_dump),
105-
colors::GREEN);
106-
} else {
107-
print_table({"Symbol", "Thunk"}, {{"Empty"}}, colors::GREEN);
101+
continue;
102+
}
103+
104+
if (line == ":h" || line == ":help" || line == ":?") {
105+
std::cout << std::endl;
106+
print_table({"General Commands", "Argument", "Description"}, {
107+
{":q, :quit, :exit", "", "Clear the screen"},
108+
{":c, :clear, :cls", "", "Exit the REPL"},
109+
{":h, :help, :?", "", "Display this help message"},
110+
{":l, :load", "<filepath>", "Load file into REPL"},
111+
{":r, :reset", "", "Reset environment"},
112+
{":d, :debug", "", "Toggle debug mode"}
113+
}, colors::GREEN);
114+
std::cout << std::endl;
115+
print_table({"Inspection Commands", "Argument", "Description"}, {
116+
{":e, :env", "", "Dump environment bindings"},
117+
{":force", "", "Force thunk evaluation on dump"}
118+
}, colors::GREEN);
119+
std::cout << std::endl;
120+
print_table({"Options", "State", "Help"}, {
121+
{"debug", on_off(options_v.debug), "use :debug to toggle"},
122+
{
123+
"force-on-env-dump", on_off(options_v.force_on_env_dump),
124+
"use :force to toggle"
125+
}
126+
}, colors::GREEN);
127+
continue;
128+
}
129+
130+
if (line == ":env" || line == ":e") {
131+
std::cout << std::endl;
132+
if (shared_global_env) {
133+
print_table({"Symbol", "Thunk"},
134+
(*shared_global_env)->to_vector(options_v.force_on_env_dump),
135+
colors::GREEN);
136+
} else {
137+
print_table({"Symbol", "Thunk"}, {{"Empty"}}, colors::GREEN);
138+
}
139+
continue;
140+
}
141+
142+
if (line == ":reset" || line == ":r") {
143+
shared_global_env.reset();
144+
continue;
145+
}
146+
147+
if (line == ":debug" || line == ":d") {
148+
options_v.debug = !options_v.debug;
149+
continue;
150+
}
151+
152+
if (line == ":force") {
153+
options_v.force_on_env_dump = !options_v.force_on_env_dump;
154+
continue;
155+
}
156+
157+
if (line.rfind(":load ", 0) == 0) {
158+
process_load_command(line.substr(6), shared_global_env);
159+
continue;
160+
}
161+
162+
if (line.rfind(":l ", 0) == 0) {
163+
process_load_command(line.substr(3), shared_global_env);
164+
continue;
108165
}
109-
continue;
110-
}
111-
if (line == ":reset" || line == ":r") {
112-
shared_global_env.reset();
113-
continue;
114-
}
115-
if (line == ":debug" || line == ":d") {
116-
options_v.debug = !options_v.debug;
117-
continue;
118-
}
119-
if (line == ":force") {
120-
options_v.force_on_env_dump = !options_v.force_on_env_dump;
121-
continue;
122-
}
123-
if (line.rfind(":load ", 0) == 0) {
124-
process_load_command(line.substr(6), shared_global_env);
125-
continue;
126166
}
127-
if (line.rfind(":l ", 0) == 0) {
128-
process_load_command(line.substr(3), shared_global_env);
129-
continue;
167+
168+
if (line.empty()) continue;
169+
170+
std::string trimmed = trim(line);
171+
// Continue the line with trailing '`'
172+
bool continuation = !trimmed.empty() && trimmed.back() == '`';
173+
if (continuation) {
174+
trimmed.pop_back();
130175
}
131-
if (line.empty()) {
176+
buffer += trimmed + "\n";
177+
178+
if (continuation) {
179+
// Indent level depends on whether the current line ends with '.' after trimming and removing trailing '`'.
180+
// Additionally, it also depends upon the current parenthesis depth.
181+
indent_level = compute_paren_depth(buffer);
182+
trimmed = trim(trimmed);
183+
if (!trimmed.empty() && trimmed.back() == '.') indent_level++;
132184
continue;
133185
}
186+
indent_level = 0;
187+
line = buffer;
188+
buffer.clear();
189+
134190
// Lex
135191
fe::lexer::Lexer lexer_v(line, fe::lexer::FromRepl{}, options_v);
136192
const std::vector<fe::token::Token> tokens = lexer_v.lex_all();
@@ -156,7 +212,7 @@ namespace repl {
156212
shared_global_env = global_env;
157213
} catch (const ControlledExit &) {
158214
} catch (const std::exception &ex) {
159-
options_v.logger.error({}, "error: ", ex.what());;
215+
options_v.logger.error({}, "error: ", ex.what());
160216
}
161217
}
162218
}

0 commit comments

Comments
 (0)