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

Commit d6ccebc

Browse files
committed
feat: add basic support for runtime type checking
1 parent bd6ec90 commit d6ccebc

6 files changed

Lines changed: 181 additions & 34 deletions

File tree

‎include/lbd/runtime/builtins.h‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,32 @@
11
#pragma once
22

33
#include <lbd/runtime/interpreter.h>
4+
#include <lbd/runtime/type.h>
45
#include <lbd/options.h>
56

67
namespace runtime::builtins {
78
extern context::Options optionsValue;
89

10+
/// Helper functions to make defining native-functions easier.
11+
using TypePointer = std::shared_ptr<type::Type>;
12+
13+
inline TypePointer simpleType(type::TypeTag tag) {
14+
return std::make_shared<type::SimpleType>(tag);
15+
}
16+
17+
inline TypePointer listType(TypePointer elementType) {
18+
return std::make_shared<type::ListType>(elementType);
19+
}
20+
21+
inline std::shared_ptr<type::FunctionType> functionType(std::vector<TypePointer> argumentTypes,
22+
TypePointer returnType,
23+
bool isVariadic = false) {
24+
return std::make_shared<type::FunctionType>(std::move(argumentTypes), std::move(returnType), isVariadic);
25+
}
26+
27+
// TODO: Move the definitions to source-file.
28+
29+
// TODO: Generate the SIGNATURE documentation on-demand, rather than editing it manually.
30+
931
std::vector<NativeFunction> getBuiltins(context::Options options_ = {});
1032
}

‎include/lbd/runtime/interpreter.h‎

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
#include <lbd/frontend/parser.h>
99
#include <lbd/options.h>
1010

11+
namespace runtime::type {
12+
struct FunctionType;
13+
}
14+
1115
namespace runtime {
1216
struct NativeFunction;
1317
struct Thunk;
@@ -62,14 +66,25 @@ namespace runtime {
6266
using Implementation = std::function<std::pair<Value, ResultOptions>(
6367
const std::vector<std::shared_ptr<Thunk> > &, const std::shared_ptr<Environment> &)>;
6468

65-
int arity;
66-
std::string name;
67-
// TODO: We can store signature of native-function here.
68-
Implementation implementation;
69+
NativeFunction(const std::string &name, const std::shared_ptr<type::FunctionType> &signature,
70+
const Implementation &implementation);
71+
72+
[[nodiscard]] std::string getName() const;
73+
74+
[[nodiscard]] std::shared_ptr<type::FunctionType> getSignature() const;
75+
76+
[[nodiscard]] Implementation getImplementation() const;
77+
78+
[[nodiscard]] int getArity() const;
6979

7080
[[nodiscard]] std::string toString() const;
7181

7282
friend std::ostream &operator<<(std::ostream &outputStream, const NativeFunction &nativeFunction);
83+
84+
private:
85+
std::string name;
86+
std::shared_ptr<type::FunctionType> signature;
87+
Implementation implementation;
7388
};
7489

7590
/// Lazy-Thunk (call-by-need).

‎include/lbd/runtime/type.h‎

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
#pragma once
22

3-
#include <lbd/runtime/interpreter.h>
3+
#include <string>
4+
#include <memory>
5+
#include <vector>
46

57
/// These are runtime-types different from frontend::type.
68

9+
/// Forward declaration of value-structure, cannot import directly due to circular-dependency.
10+
namespace runtime {
11+
struct Value;
12+
}
13+
714
namespace runtime::type {
815
enum class TypeTag {
916
Float,
@@ -21,7 +28,9 @@ namespace runtime::type {
2128
struct Type {
2229
virtual ~Type() = default;
2330

24-
[[nodiscard]] virtual bool matches(const Value &value) const = 0;
31+
[[nodiscard]] virtual bool matches(const Value &value) const;
32+
33+
[[nodiscard]] virtual bool equals(const Type &otherType) const;
2534

2635
[[nodiscard]] virtual std::string toString() const = 0;
2736

@@ -33,6 +42,8 @@ namespace runtime::type {
3342

3443
[[nodiscard]] bool matches(const Value &value) const override;
3544

45+
[[nodiscard]] bool equals(const Type &otherType) const override;
46+
3647
[[nodiscard]] std::string toString() const override;
3748

3849
[[nodiscard]] bool isCallable() const override;
@@ -46,6 +57,8 @@ namespace runtime::type {
4657

4758
[[nodiscard]] bool matches(const Value &value) const override;
4859

60+
[[nodiscard]] bool equals(const Type &otherType) const override;
61+
4962
[[nodiscard]] std::string toString() const override;
5063

5164
[[nodiscard]] bool isCallable() const override;
@@ -55,12 +68,14 @@ namespace runtime::type {
5568
};
5669

5770
struct FunctionType final : Type {
58-
// TODO: Add support for variadic, maybe by adding a default parameter 'isVariadic'.
71+
// TODO: Add support for void return-type.
5972
explicit FunctionType(const std::vector<std::shared_ptr<Type> > &argumentTypes,
60-
const std::shared_ptr<Type> &returnType);
73+
const std::shared_ptr<Type> &returnType, bool isVariadic = false);
6174

6275
[[nodiscard]] bool matches(const Value &value) const override;
6376

77+
[[nodiscard]] bool equals(const Type &otherType) const override;
78+
6479
[[nodiscard]] std::string toString() const override;
6580

6681
[[nodiscard]] bool isCallable() const override;
@@ -70,5 +85,7 @@ namespace runtime::type {
7085
private:
7186
std::vector<std::shared_ptr<Type> > argumentTypes;
7287
std::shared_ptr<Type> returnType;
88+
bool isVariadic;
89+
std::shared_ptr<Type> variadicType;
7390
};
7491
}

‎src/runtime/builtin-modules/builtin_module_list.cpp‎

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#include <lbd/runtime/builtin-modules/builtin_module_list.h>
2+
#include <lbd/runtime/builtins.h>
3+
#include <lbd/runtime/type.h>
24

35
namespace runtime::builtins {
46
static Value listGet(const std::shared_ptr<List> &list, size_t index) {
@@ -27,9 +29,11 @@ namespace runtime::builtins {
2729

2830
NativeFunction makeList() {
2931
const std::string name = "list";
32+
const auto signature = functionType({simpleType(type::TypeTag::Any)}, listType(simpleType(type::TypeTag::Any)),
33+
true);
3034
return {
31-
-1, name, [](const std::vector<std::shared_ptr<Thunk> > &arguments,
32-
const std::shared_ptr<Environment> &) -> std::pair<Value, ResultOptions> {
35+
name, signature, [](const std::vector<std::shared_ptr<Thunk> > &arguments,
36+
const std::shared_ptr<Environment> &) -> std::pair<Value, ResultOptions> {
3337
std::vector<Value> values;
3438
for (auto &argument: arguments) {
3539
values.push_back(argument->force());
@@ -41,9 +45,12 @@ namespace runtime::builtins {
4145

4246
NativeFunction makeListSize() {
4347
const std::string name = "list_size";
48+
const auto signature = functionType({listType(simpleType(type::TypeTag::Any))},
49+
simpleType(type::TypeTag::Float));
4450
return {
45-
1, name, [name](const std::vector<std::shared_ptr<Thunk> > &arguments,
46-
const std::shared_ptr<Environment> &) -> std::pair<Value, ResultOptions> {
51+
name, signature, [name](const std::vector<std::shared_ptr<Thunk> > &arguments,
52+
const std::shared_ptr<Environment> &) -> std::pair<Value, ResultOptions> {
53+
// TODO: Generalize this signature type-checking.
4754
const Value &argument0 = arguments[0]->force();
4855
if (!std::holds_alternative<std::shared_ptr<List> >(argument0)) {
4956
optionsValue.logger.error({}, "runtime error: wrong arguments provided to native function ", name,
@@ -59,10 +66,13 @@ namespace runtime::builtins {
5966

6067
NativeFunction makeListGet() {
6168
const std::string name = "list_get";
69+
const auto signature = functionType(
70+
{listType(simpleType(type::TypeTag::Any)), simpleType(type::TypeTag::Float)},
71+
simpleType(type::TypeTag::Any));
6272
return {
6373
// TODO: Add type checker to replace this manual approach.
64-
2, name, [name](const std::vector<std::shared_ptr<Thunk> > &arguments,
65-
const std::shared_ptr<Environment> &) -> std::pair<Value, ResultOptions> {
74+
name, signature, [name](const std::vector<std::shared_ptr<Thunk> > &arguments,
75+
const std::shared_ptr<Environment> &) -> std::pair<Value, ResultOptions> {
6676
const Value &argument0 = arguments[0]->force();
6777
if (!std::holds_alternative<std::shared_ptr<List> >(argument0)) {
6878
optionsValue.logger.error({}, "runtime error: wrong arguments provided to native function ", name,
@@ -87,10 +97,13 @@ namespace runtime::builtins {
8797

8898
NativeFunction makeListRemove() {
8999
const std::string name = "list_remove";
100+
const auto signature = functionType(
101+
{listType(simpleType(type::TypeTag::Any)), simpleType(type::TypeTag::Float)},
102+
simpleType(type::TypeTag::Any));
90103
return {
91104
// TODO: Add type checker to replace this manual approach
92-
2, name, [name](const std::vector<std::shared_ptr<Thunk> > &arguments,
93-
const std::shared_ptr<Environment> &) -> std::pair<Value, ResultOptions> {
105+
name, signature, [name](const std::vector<std::shared_ptr<Thunk> > &arguments,
106+
const std::shared_ptr<Environment> &) -> std::pair<Value, ResultOptions> {
94107
const Value &argument0 = arguments[0]->force();
95108
if (!std::holds_alternative<std::shared_ptr<List> >(argument0)) {
96109
optionsValue.logger.error({}, "runtime error: wrong arguments provided to native function ", name,
@@ -117,9 +130,13 @@ namespace runtime::builtins {
117130

118131
NativeFunction makeListAppend() {
119132
const std::string name = "list_append";
133+
// TODO: Update the return-type to void.
134+
const auto signature = functionType(
135+
{listType(simpleType(type::TypeTag::Any)), simpleType(type::TypeTag::Any)},
136+
simpleType(type::TypeTag::Any));
120137
return {
121-
2, name, [name](const std::vector<std::shared_ptr<Thunk> > &arguments,
122-
const std::shared_ptr<Environment> &) -> std::pair<Value, ResultOptions> {
138+
name, signature, [name](const std::vector<std::shared_ptr<Thunk> > &arguments,
139+
const std::shared_ptr<Environment> &) -> std::pair<Value, ResultOptions> {
123140
const Value &argument0 = arguments[0]->force();
124141
if (!std::holds_alternative<std::shared_ptr<List> >(argument0)) {
125142
optionsValue.logger.error({}, "runtime error: wrong arguments provided to native function ", name,

‎src/runtime/interpreter.cpp‎

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include <lbd/runtime/interpreter.h>
2+
#include <lbd/runtime/type.h>
23
#include <lbd/runtime/builtins.h>
34
#include <lbd/options.h>
45
#include <lbd/error.h>
@@ -63,10 +64,31 @@ namespace runtime {
6364
return outputStream << list.toString();
6465
}
6566

66-
[[nodiscard]] std::string NativeFunction::toString() const {
67+
NativeFunction::NativeFunction(const std::string &name, const std::shared_ptr<type::FunctionType> &signature,
68+
const Implementation &implementation) : name(name), signature(signature),
69+
implementation(implementation) {
70+
}
71+
72+
std::string NativeFunction::getName() const {
73+
return name;
74+
}
75+
76+
std::shared_ptr<type::FunctionType> NativeFunction::getSignature() const {
77+
return signature;
78+
}
79+
80+
NativeFunction::Implementation NativeFunction::getImplementation() const {
81+
return implementation;
82+
}
83+
84+
int NativeFunction::getArity() const {
85+
return signature->arity();
86+
}
87+
88+
std::string NativeFunction::toString() const {
6789
std::ostringstream outputStringStream;
6890
outputStringStream << "<NativeFunction: " << name << "(";
69-
if (arity == -1) {
91+
if (const int arity = getArity(); arity == -1) {
7092
// Variadic-Function.
7193
outputStringStream << "...";
7294
} else {
@@ -243,9 +265,9 @@ namespace runtime {
243265
if (std::holds_alternative<std::shared_ptr<NativeFunction> >(currentFunction)) {
244266
// Allow arity==0 and arity==-1 (variadic) to execute with zero args.
245267
if (const auto &nativeFunction = *std::get<std::shared_ptr<NativeFunction> >(currentFunction);
246-
nativeFunction.arity == 0 || nativeFunction.arity == -1) {
268+
nativeFunction.getArity() == 0 || nativeFunction.getArity() == -1) {
247269
std::vector<std::shared_ptr<Thunk> > slice; // empty
248-
auto [value, result_options] = nativeFunction.implementation(slice, callSiteEnvironment);
270+
auto [value, result_options] = nativeFunction.getImplementation()(slice, callSiteEnvironment);
249271
globalResultOptions.interpolate(result_options);
250272
return value;
251273
}
@@ -269,19 +291,22 @@ namespace runtime {
269291
}
270292
// Native Function case: Consumes its arity-many Argument Thunks
271293
else if (std::holds_alternative<std::shared_ptr<NativeFunction> >(currentFunction)) {
272-
if (const auto &[arity, name, implementation] = *std::get<std::shared_ptr<NativeFunction> >(
273-
currentFunction);
274-
arity != -1) {
294+
if (const auto &nativeFunction = *std::get<std::shared_ptr<NativeFunction> >(currentFunction);
295+
nativeFunction.getArity() != -1) {
296+
const int arity = nativeFunction.getArity();
297+
275298
if (workArguments.size() - index < arity) {
276-
optionsValue.logger.error(callLocation, "runtime error: native function ", name, " expects ",
299+
optionsValue.logger.error(callLocation, "runtime error: native function ",
300+
nativeFunction.getName(), " expects ",
277301
arity, " argument(s), found ", workArguments.size() - index);
278302
}
279303
std::vector<std::shared_ptr<Thunk> > slice;
280304
slice.reserve(arity);
281305
for (size_t i = 0; i < arity; ++i) {
282306
slice.push_back(workArguments[index + i]);
283307
}
284-
auto [resultantValue_, resultOptions] = implementation(slice, callSiteEnvironment);
308+
auto [resultantValue_, resultOptions] = nativeFunction.getImplementation()(
309+
slice, callSiteEnvironment);
285310
resultantValue = resultantValue_;
286311
globalResultOptions.interpolate(resultOptions);
287312
index += arity;
@@ -291,7 +316,8 @@ namespace runtime {
291316
for (size_t i = 0; i < arguments.size() - index; ++i) {
292317
slice.push_back(workArguments[index + i]);
293318
}
294-
auto [resultantValue_, resultOptions] = implementation(slice, callSiteEnvironment);
319+
auto [resultantValue_, resultOptions] = nativeFunction.getImplementation()(
320+
slice, callSiteEnvironment);
295321
resultantValue = resultantValue_;
296322
globalResultOptions.interpolate(resultOptions);
297323
index += arguments.size() - index;
@@ -379,7 +405,7 @@ namespace runtime {
379405
for (auto &nativeFunction: builtins::getBuiltins(optionsValue)) {
380406
const auto thunk = std::make_shared<Thunk>();
381407
thunk->cached = Value{std::make_shared<NativeFunction>(nativeFunction)};
382-
environment->bind(nativeFunction.name, thunk);
408+
environment->bind(nativeFunction.getName(), thunk);
383409
}
384410
}
385411
}

0 commit comments

Comments
 (0)