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

Commit eab1b39

Browse files
committed
feat: rudimentary support for lists
1 parent 2970f77 commit eab1b39

9 files changed

Lines changed: 133 additions & 52 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,5 @@ lbd [options]
4949

5050
## TODO
5151

52-
- TODO: Provide better examples (e.g., Advent of Code snippets).
52+
- TODO: Provide better examples (e.g., Advent of Code snippets).
53+
- TODO: Maybe use rere.py for testing

‎SPEC_DRAFT.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- TODO: Support parsing code that ends with an id. for example "a", given is already defined previously.
2+
- TODO: Add support for List at a language level.
3+
- TODO: Type checking.
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
true: Bool = \x: Any. \y: Any. x
22
false: Bool = \x: Any. \y: Any. y
33

4-
ifthen: Any = \condition: Bool -> Any -> Any -> Any.
4+
if_then: Any = \condition: Any.
55
\then_clause: Any. \else_clause: Any.
66
(condition then_clause else_clause)
77

8-
(print (ifthen true "True" "False"))
9-
(print (ifthen false "True" "False"))
8+
9+
(print (if_then true "True" "False"))
10+
(print (if_then false "True" "False"))

‎examples/list.lbd‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
a: List = (list "Hello")
2+
(print a)
3+
4+
(list_append a "World")
5+
(print a)
6+
7+
-- TODO: Add support for list_get or nth NativeFunction

‎examples/std.lbd‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ if_then: Any = \condition: Any.
66
\then_clause: Any. \else_clause: Any.
77
(condition then_clause else_clause)
88

9+
bool_name: Bool = \bool: Bool. (if_then bool "true" "false")
10+
911
-- pairs
1012
pair: Any = \x: Any. \y: Any. \fn: Any. (fn x y)
1113

‎include/lbd/intp/interpreter.h‎

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,52 @@
33
#include <functional>
44
#include <optional>
55
#include <string>
6+
#include <variant>
67
#include <lbd/fe/ast.h>
78
#include <lbd/fe/parser.h>
89

910
namespace intp::interp {
1011
struct NativeFunction;
1112
struct Thunk;
1213
struct Env;
14+
struct List;
15+
struct Value;
1316

1417
/// Runtime representation of Lambda Expression
1518
struct Closure {
1619
std::string param;
1720
const fe::ast::Expression *body; /// Non-owning, read-only AST pointer
1821
std::shared_ptr<Env> env; /// Environment at the time of Lambda Expression creation
1922

23+
[[nodiscard]] std::string to_string() const;
24+
2025
friend std::ostream &operator<<(std::ostream &os, const Closure &closure);
26+
};
27+
28+
struct List {
29+
std::vector<Value> elements;
2130

2231
[[nodiscard]] std::string to_string() const;
32+
33+
friend std::ostream &operator<<(std::ostream &os, const List &list);
2334
};
2435

25-
using Value = std::variant<double, std::string, Closure, NativeFunction>;
36+
using ValueVariant = std::variant<
37+
double,
38+
std::string,
39+
Closure,
40+
std::shared_ptr<NativeFunction>,
41+
std::shared_ptr<List>
42+
>;
43+
44+
struct Value : ValueVariant {
45+
using ValueVariant::ValueVariant; // inherit constructors
46+
47+
/// Pretty print a runtime value for REPL/diagnostics
48+
[[nodiscard]] std::string to_string() const;
49+
50+
friend std::ostream &operator<<(std::ostream &os, const Value &value);
51+
};
2652

2753
struct NativeFunction {
2854
using Impl = std::function<Value(
@@ -32,15 +58,10 @@ namespace intp::interp {
3258
std::string name;
3359
Impl impl;
3460

35-
friend std::ostream &operator<<(std::ostream &os, const NativeFunction &native_fn);
36-
3761
[[nodiscard]] std::string to_string() const;
38-
};
39-
40-
/// Pretty print a runtime value for REPL/diagnostics
41-
std::string val_to_string(const Value &value);
4262

43-
std::ostream &operator<<(std::ostream &os, const Value &value);
63+
friend std::ostream &operator<<(std::ostream &os, const NativeFunction &native_fn);
64+
};
4465

4566
/// Lazy Thunk (call-by-need)
4667
struct Thunk : std::enable_shared_from_this<Thunk> {

‎src/intp/builtins.cpp‎

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace intp::interp {
88
return {
99
1, name, [](const std::vector<std::shared_ptr<Thunk> > &args, const std::shared_ptr<Env> &) -> Value {
1010
const Value &value = args[0]->force();
11-
std::cout << value << std::endl;
11+
std::cout << value.to_string() << std::endl;
1212
return value;
1313
}
1414
};
@@ -78,7 +78,7 @@ namespace intp::interp {
7878
}
7979
const double num1 = std::get<double>(value1);
8080
const double num2 = std::get<double>(value2);
81-
const int result = (num1 < num2) ? -1 : num1 > num2 ? 1 : 0;
81+
const int result = num1 < num2 ? -1 : num1 > num2 ? 1 : 0;
8282
return Value{static_cast<double>(result)};
8383
}
8484
};
@@ -104,6 +104,43 @@ namespace intp::interp {
104104
};
105105
}
106106

107+
static std::shared_ptr<List> make_list_obj(const std::vector<Value> &elements) {
108+
return std::make_shared<List>(List{elements});
109+
}
110+
111+
static Value list_get(const std::shared_ptr<List> &lst, size_t index) {
112+
if (index >= lst->elements.size())
113+
throw std::out_of_range("list index out of range");
114+
return lst->elements[index];
115+
}
116+
117+
static void list_append(std::shared_ptr<List> &lst, Value val) {
118+
lst->elements.push_back(std::move(val));
119+
}
120+
121+
static NativeFunction make_list() {
122+
const std::string name = "list";
123+
// TODO: Make this variadic
124+
return {
125+
1, name, [](const std::vector<std::shared_ptr<Thunk> > &args, const std::shared_ptr<Env> &) -> Value {
126+
std::vector<Value> vals;
127+
for (auto &th: args) vals.push_back(th->force());
128+
return make_list_obj(vals);
129+
}
130+
};
131+
}
132+
133+
static NativeFunction make_list_append() {
134+
const std::string name = "list_append";
135+
return {
136+
2, name, [](const std::vector<std::shared_ptr<Thunk> > &args, const std::shared_ptr<Env> &) -> Value {
137+
// TODO: Add error handling
138+
auto lst = std::get<std::shared_ptr<List> >(args[0]->force());
139+
list_append(lst, args[1]->force());
140+
return lst;
141+
}
142+
};
143+
}
107144

108145
std::vector<NativeFunction> get_builtins() {
109146
return {
@@ -112,7 +149,9 @@ namespace intp::interp {
112149
{make_sub()},
113150
{make_mul()},
114151
{make_cmp()},
115-
{make_if_zero()}
152+
{make_if_zero()},
153+
{make_list()},
154+
{make_list_append()}
116155
};
117156
}
118157
}

‎src/intp/interpreter.cpp‎

Lines changed: 43 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,51 +14,59 @@ namespace intp::interp {
1414
return os << closure.to_string();
1515
}
1616

17-
[[nodiscard]] std::string NativeFunction::to_string() const {
17+
[[nodiscard]] std::string List::to_string() const {
1818
std::ostringstream oss;
19-
oss << "<native_fn: " << name << " " << arity << ">";
20-
return oss.str();
21-
}
22-
23-
std::ostream &operator<<(std::ostream &os, const NativeFunction &native_fn) {
24-
return os << native_fn.to_string();
25-
}
26-
27-
static std::ostream &print_loc_prefix(std::ostream &os, const std::optional<fe::loc::Loc> &loc) {
28-
if (loc.has_value()) {
29-
return os << loc.value() << ": ";
19+
oss << "[";
20+
for (size_t i = 0; i < elements.size(); ++i) {
21+
oss << elements[i].to_string();
22+
if (i + 1 != elements.size()) {
23+
oss << ", ";
24+
}
3025
}
31-
return os;
26+
oss << "]";
27+
return oss.str();
3228
}
3329

34-
std::string val_to_string(const Value &value) {
30+
[[nodiscard]] std::string Value::to_string() const {
3531
return std::visit([&]<typename T0>(T0 &&arg) {
3632
using T = std::decay_t<T0>;
3733
if constexpr (std::is_same_v<T, double>) {
3834
return std::to_string(arg);
3935
} else if constexpr (std::is_same_v<T, std::string>) {
4036
return arg;
41-
} else if constexpr (std::is_same_v<T, Closure> ||
42-
std::is_same_v<T, NativeFunction>) {
37+
} else if constexpr (std::is_same_v<T, Closure>) {
4338
return arg.to_string();
39+
} else if constexpr (std::is_same_v<T, std::shared_ptr<NativeFunction> > ||
40+
std::is_same_v<T, std::shared_ptr<List> >) {
41+
return arg->to_string();
4442
} else {
4543
STATIC_ASSERT_UNREACHABLE_T(T, "unhandled runtime value");
4644
}
47-
}, value);
45+
}, *this);
4846
}
4947

5048
std::ostream &operator<<(std::ostream &os, const Value &value) {
51-
std::visit([&]<typename T0>(T0 &&arg) {
52-
using T = std::decay_t<T0>;
53-
if constexpr (std::is_same_v<T, std::string> ||
54-
std::is_same_v<T, double> ||
55-
std::is_same_v<T, Closure> ||
56-
std::is_same_v<T, NativeFunction>) {
57-
os << arg;
58-
} else {
59-
STATIC_ASSERT_UNREACHABLE_T(T, "unhandled runtime value");
60-
}
61-
}, value);
49+
return os << value.to_string();
50+
}
51+
52+
std::ostream &operator<<(std::ostream &os, const List &list) {
53+
return os << list.to_string();
54+
}
55+
56+
[[nodiscard]] std::string NativeFunction::to_string() const {
57+
std::ostringstream oss;
58+
oss << "<native_fn: " << name << " " << arity << ">";
59+
return oss.str();
60+
}
61+
62+
std::ostream &operator<<(std::ostream &os, const NativeFunction &native_fn) {
63+
return os << native_fn.to_string();
64+
}
65+
66+
static std::ostream &print_loc_prefix(std::ostream &os, const std::optional<fe::loc::Loc> &loc) {
67+
if (loc.has_value()) {
68+
return os << loc.value() << ": ";
69+
}
6270
return os;
6371
}
6472

@@ -128,12 +136,11 @@ namespace intp::interp {
128136
try {
129137
if (force) {
130138
const Value &val = thunk->force();
131-
val_str = val_to_string(val);
139+
val_str = val.to_string();
132140
} else if (thunk->cached) {
133-
val_str = val_to_string(*thunk->cached); // already computed
141+
val_str = thunk->cached->to_string(); // already computed
134142
}
135143
} catch (const std::exception &) {
136-
val_str = "<thunk: error>";
137144
}
138145
vec.push_back({name, val_str});
139146
}
@@ -239,8 +246,8 @@ namespace intp::interp {
239246
resultant_value = eval_expr(*body, child_env);
240247
}
241248
// Native Function case: Consumes its arity-many Argument Thunks
242-
else if (std::holds_alternative<NativeFunction>(current_fn)) {
243-
const auto &[arity, name, impl] = std::get<NativeFunction>(current_fn);
249+
else if (std::holds_alternative<std::shared_ptr<NativeFunction> >(current_fn)) {
250+
const auto &[arity, name, impl] = *std::get<std::shared_ptr<NativeFunction> >(current_fn);
244251
if (work_args.size() - idx < arity) {
245252
print_loc_prefix(std::cerr, call_loc)
246253
<< "runtime error: native function " << name
@@ -268,7 +275,7 @@ namespace intp::interp {
268275
std::exit(EXIT_FAILURE);
269276
}
270277
if (std::holds_alternative<Closure>(*resultant_value) ||
271-
std::holds_alternative<NativeFunction>(*resultant_value)) {
278+
std::holds_alternative<std::shared_ptr<NativeFunction> >(*resultant_value)) {
272279
// Replace the top Frame with the returned Function (curry)
273280
frames.back() = std::move(*resultant_value);
274281
continue;
@@ -302,7 +309,7 @@ namespace intp::interp {
302309

303310
// Creates placeholder Thunk then set body so recursion can refer to it during lazy evaluation
304311
static void bind_def_ast_node_lazy(fe::ast::DefAstNode &def_ast_node, const std::shared_ptr<Env> &env,
305-
Options options) {
312+
const Options options) {
306313
const auto thunk = std::make_shared<Thunk>();
307314
env->bind(def_ast_node.def_name.value, thunk);
308315
if (options.own_expr) {
@@ -339,7 +346,7 @@ namespace intp::interp {
339346
void install_builtins(const std::shared_ptr<Env> &env) {
340347
for (auto &native_fn: get_builtins()) {
341348
const auto thunk = std::make_shared<Thunk>();
342-
thunk->cached = Value{native_fn};
349+
thunk->cached = Value{std::make_shared<NativeFunction>(native_fn)};
343350
env->bind(native_fn.name, thunk);
344351
}
345352
}

‎src/repl.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ namespace repl {
5858
break; // EOF (C-d / C-z)
5959
}
6060
if (line == ":q" || line == ":quit" || line == ":exit") {
61-
std::cout << colors::BLUE << "exiting REPL" << colors::RESET << std::endl;
61+
std::cout << "\n" << colors::BLUE << "exiting REPL" << colors::RESET << std::endl;
6262
break;
6363
}
6464
if (line == ":c" || line == ":clear" || line == ":cls") {

0 commit comments

Comments
 (0)