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

Commit 2970f77

Browse files
committed
feat: add REPL
1 parent e64c31e commit 2970f77

9 files changed

Lines changed: 254 additions & 91 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ lbd [options]
3333
### Options
3434

3535
```
36-
-f, --file <filename> Specify input source file to run
36+
-f, --file <filepath> Specify input source filepath to run
3737
-h, --help Show this help message and exit
3838
-d, --debug Enable debug mode
3939
-r, --repl Run in interactive REPL mode

‎examples/std.lbd‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
-- TODO: Support for `use`
2-
31
-- booleans and conditions
42
true: Bool = \x: Any. \y: Any. x
53
false: Bool = \x: Any. \y: Any. y

‎examples/test.lbd‎

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,3 @@
1-
-- Define Pi as constant
2-
pi: Float = 3.14
1+
use "examples/std.lbd"
32

4-
-- Define function to calculate square
5-
square: Float -> Float = \x: Float. (mul x x)
6-
7-
-- Define function to calculate area of circle
8-
area_of_circle: Float -> Float = \r: Float.
9-
(mul pi (square r))
10-
11-
-- Define function to calculate volume of cylinder
12-
volume_of_cylinder: Float -> Float -> Float = \r: Float.
13-
\h: Float. (mul (area_of_circle r) h)
14-
15-
-- Calculate the volume of a cylinder
16-
(print "Volume of cylinder is: ")
17-
(print (volume_of_cylinder 5.0 10.0))
3+
(print (eq 1 0))

‎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/cmd.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ 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"
7+
<< " -f, --file <filepath> Specify input source filepath to run\n"
88
<< " -h, --help Show this help message and exit\n"
99
<< " -d, --debug Enable debug mode\n"
1010
<< " -r, --repl Run in interactive REPL node" << std::endl;

‎src/fe/parser.cpp‎

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
#include <lbd/fe/loc.h>
2+
#include <lbd/fe/lexer.h>
23
#include <lbd/fe/parser.h>
34
#include <lbd/string_escape.h>
5+
#include <unordered_set>
6+
#include <filesystem>
47

58
namespace fe::parser {
9+
static std::unordered_set<std::string> loaded_files;
10+
611
Parser::Parser(const std::vector<token::Token> &tokens) {
712
size_t i = 0;
813
program = ast::Program{build_ast(tokens, i)};
@@ -137,26 +142,53 @@ namespace fe::parser {
137142
return ast::DefAstNode{def_name, typ, std::move(expr), loc};
138143
}
139144

145+
static std::string get_abs_path(const std::string &path) {
146+
return std::filesystem::absolute(path).string();
147+
}
148+
149+
static void process_use_file(std::vector<ast::AstNode> &nodes, const std::string &filepath) {
150+
const std::string abs_path = get_abs_path(filepath);
151+
if (loaded_files.contains(abs_path)) {
152+
// Circular Dependency or Duplicate Load
153+
return;
154+
}
155+
loaded_files.insert(abs_path);
156+
auto lexer_v = lexer::Lexer(filepath, lexer::FromFile{});
157+
const auto tokens = lexer_v.lex_all();
158+
// TODO: Accept Options and Add a debug flag for printing Lexed Tokens
159+
for (Parser parser(tokens); auto &node: parser.program.nodes) {
160+
nodes.emplace_back(std::move(node));
161+
}
162+
}
163+
140164
std::vector<ast::AstNode> Parser::build_ast(const std::vector<token::Token> &tokens, size_t &i) {
141165
std::vector<ast::AstNode> nodes;
142166
while (!std::holds_alternative<token::Eof>(tokens[i].typ)) {
143167
const token::Token &tok = tokens[i];
144-
nodes.push_back(std::visit([&]<typename T0>(T0 &&) {
168+
std::visit([&]<typename T0>(T0 &&) {
145169
using T = std::decay_t<T0>;
146-
if (std::is_same_v<T, token::Iden>) {
147-
return ast::AstNode{std::move(parse_def_ast_node(tokens, i))};
148-
}
149-
if (std::is_same_v<T, token::String>
150-
|| std::is_same_v<T, token::Float>
151-
|| std::is_same_v<T, token::BackwardSlash>
152-
|| std::is_same_v<T, token::OpenParen>) {
153-
return ast::AstNode{std::move(parse_expression(tokens, i))};
170+
if constexpr (std::is_same_v<T, token::Iden>) {
171+
if (const auto iden_value = std::get<token::Iden>(tokens[i].typ).value; iden_value == "use") {
172+
++i; // eat "use"
173+
assert_token<token::String>(tokens, i);
174+
const std::string filepath = unescape_string(std::get<token::String>(tokens[i].typ).value);
175+
++i; // eat <filepath>
176+
process_use_file(nodes, filepath);
177+
} else {
178+
nodes.push_back(ast::AstNode{std::move(parse_def_ast_node(tokens, i))});
179+
}
180+
} else if constexpr (std::is_same_v<T, token::String> ||
181+
std::is_same_v<T, token::Float> ||
182+
std::is_same_v<T, token::BackwardSlash> ||
183+
std::is_same_v<T, token::OpenParen>) {
184+
nodes.push_back(ast::AstNode{std::move(parse_expression(tokens, i))});
185+
} else {
186+
std::cerr << tok.loc << ": syntax error: unexpected token "
187+
<< tok.to_string()
188+
<< std::endl;
189+
std::exit(EXIT_FAILURE);
154190
}
155-
std::cerr << tok.loc << ": syntax error: unexpected token "
156-
<< tok.to_string()
157-
<< std::endl;
158-
std::exit(EXIT_FAILURE);
159-
}, tok.typ));
191+
}, tok.typ);
160192
}
161193
return nodes;
162194
}

‎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) {

0 commit comments

Comments
 (0)