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

Commit 3633fed

Browse files
committed
Update REPL
1 parent 7b03e2b commit 3633fed

5 files changed

Lines changed: 148 additions & 36 deletions

File tree

‎include/lbd/intp/interpreter.h‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ namespace intp::interp {
4040
struct Thunk : std::enable_shared_from_this<Thunk> {
4141
mutable std::optional<Value> cached;
4242
const fe::ast::Expression *expr = nullptr; /// Non-owning, read-only AST pointer
43+
std::unique_ptr<fe::ast::Expression> owned; /// Owning storage (when needed) (primarily in REPL)
4344
std::shared_ptr<Env> env; /// Environment for evaluating Expression
4445
std::optional<fe::loc::Loc> origin = std::nullopt;
4546

@@ -55,6 +56,9 @@ namespace intp::interp {
5556
/// Allows for recursive reference
5657
void set(const fe::ast::Expression *expr_, std::shared_ptr<Env> env_,
5758
std::optional<fe::loc::Loc> origin_ = std::nullopt);
59+
60+
void set_owned(fe::ast::Expression expr_, std::shared_ptr<Env> env_,
61+
std::optional<fe::loc::Loc> origin_ = std::nullopt);
5862
};
5963

6064
struct Env : std::enable_shared_from_this<Env> {
@@ -66,6 +70,8 @@ namespace intp::interp {
6670
std::shared_ptr<Thunk> lookup(const std::string &name) const;
6771

6872
void bind(const std::string &name, std::shared_ptr<Thunk> thunk);
73+
74+
void dump(std::ostream &os, bool force) const;
6975
};
7076

7177
Value eval_expr(const fe::ast::Expression &expr, std::shared_ptr<Env> env);
@@ -80,7 +86,12 @@ namespace intp::interp {
8086
Value value;
8187
};
8288

83-
Result interpret(const fe::ast::Program &program, std::optional<std::shared_ptr<Env> > global_env = std::nullopt);
89+
struct Options {
90+
bool own_expr = false;
91+
};
92+
93+
Result interpret(fe::ast::Program &program, std::optional<std::shared_ptr<Env> > global_env = std::nullopt,
94+
Options options = {});
8495

8596
/// Add builtins Native Functions into Environment
8697
void install_builtins(const std::shared_ptr<Env> &env);

‎src/cmd.cpp‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ namespace cmd {
44
void print_help(std::ostream &os, const std::string &program_name) {
55
os << "usage: " << program_name << " [options]\n\n"
66
<< "options:\n"
7-
<< "-f, --file <filename> Specify input source file to run\n"
8-
<< "-h, --help Show this help message and exit\n"
9-
<< "-d, --debug Enable debug mode\n"
10-
<< "-r, --repl Run in interactive REPL node" << std::endl;
7+
<< " -f, --file <filename> Specify input source file to run\n"
8+
<< " -h, --help Show this help message and exit\n"
9+
<< " -d, --debug Enable debug mode\n"
10+
<< " -r, --repl Run in interactive REPL node" << std::endl;
1111
}
1212

1313
Options parse_args(const int argc, char **argv, const std::string &program_name) {

‎src/intp/interpreter.cpp‎

Lines changed: 68 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,18 @@
44

55
namespace intp::interp {
66
std::ostream &operator<<(std::ostream &os, const Closure &closure) {
7-
return os << "<closure " << closure.param << ">";
7+
return os << "<closure: " << closure.param << ">";
88
}
99

1010
std::ostream &operator<<(std::ostream &os, const NativeFunction &native_fn) {
11-
return os << "<native_fn " << native_fn.name << " " << native_fn.arity << ">";
11+
return os << "<native_fn: " << native_fn.name << " " << native_fn.arity << ">";
12+
}
13+
14+
static std::ostream &print_loc_prefix(std::ostream &os, const std::optional<fe::loc::Loc> &loc) {
15+
if (loc.has_value()) {
16+
return os << loc.value() << ": ";
17+
}
18+
return os;
1219
}
1320

1421
std::ostream &operator<<(std::ostream &os, const Value &value) {
@@ -36,10 +43,8 @@ namespace intp::interp {
3643
}
3744
// Expression is not initialized
3845
if (!expr) {
39-
if (origin) {
40-
std::cerr << *origin << ": ";
41-
}
42-
std::cerr << "runtime error: forcing empty thunk";
46+
print_loc_prefix(std::cerr, origin)
47+
<< "runtime error: forcing empty thunk";
4348
exit(EXIT_FAILURE);
4449
}
4550
cached = eval_expr(*expr, env);
@@ -49,9 +54,21 @@ namespace intp::interp {
4954
void Thunk::set(const fe::ast::Expression *expr_, std::shared_ptr<Env> env_,
5055
std::optional<fe::loc::Loc> origin_) {
5156
expr = expr_;
57+
owned.reset();
5258
env = std::move(env_);
53-
if (origin_) {
54-
origin = std::move(origin_);
59+
if (origin_.has_value()) {
60+
origin = std::move(origin_.value());
61+
}
62+
cached.reset();
63+
}
64+
65+
void Thunk::set_owned(fe::ast::Expression expr_, std::shared_ptr<Env> env_,
66+
std::optional<fe::loc::Loc> origin_) {
67+
owned = std::make_unique<fe::ast::Expression>(std::move(expr_));
68+
expr = owned.get();
69+
env = std::move(env_);
70+
if (origin_.has_value()) {
71+
origin = std::move(origin_.value());
5572
}
5673
cached.reset();
5774
}
@@ -74,11 +91,36 @@ namespace intp::interp {
7491
table[name] = std::move(thunk);
7592
}
7693

94+
void Env::dump(std::ostream &os, const bool force) const {
95+
for (const auto &[name, thunk]: table) {
96+
os << " " << name << " = ";
97+
try {
98+
if (force) {
99+
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+
}
107+
}
108+
} catch (const std::exception &ex) {
109+
os << "<thunk: unevaluated>";
110+
}
111+
os << std::endl;
112+
}
113+
if (parent) {
114+
os << "info: parent environment" << std::endl;
115+
parent->dump(os, force);
116+
}
117+
}
118+
77119
static Value eval_iden_ast_node(const fe::ast::IdenAstNode &iden_ast_node, const std::shared_ptr<Env> &env) {
78120
const auto thunk = env->lookup(iden_ast_node.value);
79121
if (!thunk) {
80-
std::cerr << iden_ast_node.loc << ": runtime error: "
81-
<< "undefined identifier "
122+
print_loc_prefix(std::cerr, iden_ast_node.loc)
123+
<< "runtime error: undefined identifier "
82124
<< iden_ast_node.value
83125
<< std::endl;
84126
exit(EXIT_FAILURE);
@@ -94,8 +136,8 @@ namespace intp::interp {
94136
// Lookup the callee lazily
95137
const auto callee_thunk = env->lookup(fn_apl.fn_name.value);
96138
if (!callee_thunk) {
97-
std::cerr << fn_apl.loc << ": runtime error: "
98-
<< "undefined function "
139+
print_loc_prefix(std::cerr, fn_apl.loc)
140+
<< "runtime error: undefined function "
99141
<< fn_apl.fn_name.value
100142
<< std::endl;
101143
exit(EXIT_FAILURE);
@@ -133,13 +175,6 @@ namespace intp::interp {
133175
return t;
134176
}
135177

136-
static std::ostream &print_loc_prefix(std::ostream &os, const std::optional<fe::loc::Loc> &loc) {
137-
if (loc) {
138-
return os << *loc << ": ";
139-
}
140-
return os;
141-
}
142-
143178
Value apply_fn_apl(Value fn_value, const std::vector<std::shared_ptr<Thunk> > &args,
144179
const std::shared_ptr<Env> &call_site_env, const std::optional<fe::loc::Loc> &call_loc) {
145180
// Local mutable copy of the args, for inserting evaluated values as Thunks when needed.
@@ -237,25 +272,33 @@ namespace intp::interp {
237272
}
238273

239274
// Creates placeholder Thunk then set body so recursion can refer to it during lazy evaluation
240-
static void bind_def_ast_node_lazy(const fe::ast::DefAstNode &def_ast_node, const std::shared_ptr<Env> &env) {
275+
static void bind_def_ast_node_lazy(fe::ast::DefAstNode &def_ast_node, const std::shared_ptr<Env> &env,
276+
Options options) {
241277
const auto thunk = std::make_shared<Thunk>();
242278
env->bind(def_ast_node.def_name.value, thunk);
243-
thunk->set(&def_ast_node.expr, env, def_ast_node.expr.get_loc());
279+
if (options.own_expr) {
280+
thunk->set_owned(std::move(def_ast_node.expr), env, def_ast_node.expr.get_loc());
281+
} else {
282+
thunk->set(&def_ast_node.expr, env, def_ast_node.expr.get_loc());
283+
}
244284
}
245285

246-
Result interpret(const fe::ast::Program &program, std::optional<std::shared_ptr<Env> > global_env) {
286+
Result interpret(fe::ast::Program &program, std::optional<std::shared_ptr<Env> > global_env,
287+
Options options) {
247288
if (!global_env) {
248289
global_env = std::make_shared<Env>();
290+
install_builtins(*global_env);
249291
}
250292
Value result_value;
251-
install_builtins(*global_env);
252-
for (const auto &[value]: program.nodes) {
293+
for (auto &[value]: program.nodes) {
253294
std::visit([&]<typename T0>(T0 &&arg) {
254295
using T = std::decay_t<T0>;
255296
if constexpr (std::is_same_v<T, fe::ast::Expression>) {
256297
result_value = eval_expr(arg, *global_env);
257298
} else if constexpr (std::is_same_v<T, fe::ast::DefAstNode>) {
258-
bind_def_ast_node_lazy(arg, *global_env);
299+
bind_def_ast_node_lazy(arg, *global_env, options);
300+
const fe::ast::DefAstNode &def_ast_node = arg;
301+
result_value = def_ast_node.def_name.value;
259302
} else {
260303
STATIC_ASSERT_UNREACHABLE_T(T, "unhandled program node");
261304
}

‎src/main.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ int main(const int argc, char **argv) {
2828
}
2929
}
3030
// Parse
31-
const auto parser = fe::parser::Parser(tokens);
31+
auto parser = fe::parser::Parser(tokens);
3232
if (debug) {
3333
std::cout << parser.program << std::endl;
3434
}

‎src/repl.cpp‎

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,80 @@
66
#include "lbd/intp/interpreter.h"
77

88
namespace repl {
9+
static bool force_on_env_dump = false;
10+
static bool debug_enabled = false;
11+
12+
static void help() {
13+
std::cout
14+
<< "available REPL commands:\n"
15+
<< " general:\n"
16+
<< " :q, :quit, :exit Exit the REPL\n"
17+
<< " :c, :clear, :cls Clear the screen\n"
18+
<< " :h, :help, :? Show this help message\n"
19+
<< " :r, :reload Reset environment (forget all bindings)\n"
20+
<< " :d, :debug Toggle debug mode\n"
21+
<< " inspection:\n"
22+
<< " :e, :env Show all variables in current environment\n"
23+
<< " :force Toggle forcing evaluation when dumping env\n"
24+
<< "current options:\n"
25+
<< " debug [" << (debug_enabled ? "on " : "off") << "] (use :debug to toggle)\n"
26+
<< " force-on-env-dump [" << (force_on_env_dump ? "on " : "off") << "] (use :force to toggle)"
27+
<< std::endl;
28+
}
29+
930
void loop(const bool debug) {
10-
std::cout << "Lambda Discipline REPL. Type :quit to exit." << std::endl;
31+
debug_enabled = debug;
32+
std::cout << "lambda-discipline REPL.\ntype :quit to exit." << std::endl;
1133
std::string line;
1234
std::optional<std::shared_ptr<intp::interp::Env> > shared_global_env = std::nullopt;
1335
while (true) {
1436
std::cout << "lbd> ";
1537
if (!std::getline(std::cin, line)) {
1638
break; // EOF (C-d / C-z)
1739
}
18-
if (line == ":quit" || line == ":q" || line == ":exit") {
19-
std::cout << "Exiting REPL...\n";
40+
if (line == ":q" || line == ":quit" || line == ":exit") {
41+
std::cout << "exiting REPL" << std::endl;
2042
break;
2143
}
44+
if (line == ":c" || line == ":clear" || line == ":cls") {
45+
#ifdef _WIN32
46+
std::system("cls");
47+
#else
48+
std::system("clear");
49+
#endif
50+
continue;
51+
}
52+
if (line == ":h" || line == ":help" || line == ":?") {
53+
help();
54+
continue;
55+
}
56+
if (line == ":env" || line == ":e") {
57+
if (shared_global_env) {
58+
std::cout << "info: environment bindings:" << std::endl;
59+
(*shared_global_env)->dump(std::cout, force_on_env_dump);
60+
} else {
61+
std::cout << "info: environment is empty" << std::endl;
62+
}
63+
continue;
64+
}
65+
if (line == ":reload" || line == ":r") {
66+
shared_global_env.reset();
67+
std::cout << "info: environment reloaded" << std::endl;
68+
continue;
69+
}
70+
if (line == ":debug") {
71+
debug_enabled = !debug_enabled;
72+
continue;
73+
}
74+
if (line == ":force") {
75+
force_on_env_dump = !force_on_env_dump;
76+
continue;
77+
}
2278
if (line.empty()) {
2379
continue;
2480
}
2581
try {
82+
// TODO: In case of error do not EXIT from the program
2683
// Lex
2784
fe::lexer::Lexer lexer(line, fe::lexer::FromRepl{});
2885
const std::vector<fe::token::Token> tokens = lexer.lex_all();
@@ -37,8 +94,9 @@ namespace repl {
3794
std::cout << parser.program << "\n";
3895
}
3996
// Interpret
40-
const auto [global_env, value] = intp::interp::interpret(parser.program, shared_global_env);
41-
std::cout << "=> " << value << "\n";
97+
const auto [global_env, value] = intp::interp::interpret(
98+
parser.program, shared_global_env, {.own_expr = true});
99+
std::cout << "===> " << value << "\n";
42100
shared_global_env = global_env;
43101
} catch (const std::exception &ex) {
44102
std::cerr << "error: " << ex.what() << "\n";

0 commit comments

Comments
 (0)