Scoped variables, single namespace variables and functions
This commit is contained in:
parent
0f9c46dffb
commit
5d51d1672f
1
Makefile
1
Makefile
@ -3,6 +3,7 @@ CXXFLAGS:=$(CXXFLAGS) -std=c++20 -Wall -Wextra -Werror=switch -Werror=return-typ
|
|||||||
CPPFLAGS:=$(CPPFLAGS) -Ilib/expected/ -Ilib/ut/ -Isrc/
|
CPPFLAGS:=$(CPPFLAGS) -Ilib/expected/ -Ilib/ut/ -Isrc/
|
||||||
|
|
||||||
Obj= \
|
Obj= \
|
||||||
|
bin/environment.o \
|
||||||
bin/errors.o \
|
bin/errors.o \
|
||||||
bin/interpreter.o \
|
bin/interpreter.o \
|
||||||
bin/lexer.o \
|
bin/lexer.o \
|
||||||
|
2
examples/variables.mq
Normal file
2
examples/variables.mq
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
var x = 10;
|
||||||
|
say (x + 1)
|
49
src/environment.cc
Normal file
49
src/environment.cc
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#include <musique.hh>
|
||||||
|
|
||||||
|
std::vector<Env> *Env::pool = nullptr;
|
||||||
|
|
||||||
|
Env& Env::force_define(std::string name, Value new_value)
|
||||||
|
{
|
||||||
|
variables.insert_or_assign(std::move(name), std::move(new_value));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Env& Env::parent()
|
||||||
|
{
|
||||||
|
return (*pool)[parent_enviroment_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
Value* Env::find(std::string const& name)
|
||||||
|
{
|
||||||
|
for (Env *prev = nullptr, *env = this; env != prev; prev = std::exchange(env, &env->parent())) {
|
||||||
|
if (auto it = env->variables.find(name); it != env->variables.end()) {
|
||||||
|
return &it->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
usize Env::operator++() const
|
||||||
|
{
|
||||||
|
auto const parent_id = this - pool->data();
|
||||||
|
auto const free = std::find_if(pool->begin(), pool->end(), [](Env const& env) { return env.parent_enviroment_id == Env::Unused; });
|
||||||
|
Env* next = free == pool->end()
|
||||||
|
? &pool->emplace_back()
|
||||||
|
: &*free;
|
||||||
|
|
||||||
|
next->parent_enviroment_id = parent_id;
|
||||||
|
return next - pool->data();
|
||||||
|
}
|
||||||
|
|
||||||
|
usize Env::operator--()
|
||||||
|
{
|
||||||
|
if (this == pool->data())
|
||||||
|
return 0;
|
||||||
|
variables.clear();
|
||||||
|
return std::exchange(parent_enviroment_id, Unused);
|
||||||
|
}
|
||||||
|
|
||||||
|
Env& Env::global()
|
||||||
|
{
|
||||||
|
return (*pool)[0];
|
||||||
|
}
|
@ -71,6 +71,7 @@ std::ostream& operator<<(std::ostream& os, Error const& err)
|
|||||||
|
|
||||||
case errors::Expected_Keyword:
|
case errors::Expected_Keyword:
|
||||||
case errors::Function_Not_Defined:
|
case errors::Function_Not_Defined:
|
||||||
|
case errors::Not_Callable:
|
||||||
case errors::Unexpected_Token_Type:
|
case errors::Unexpected_Token_Type:
|
||||||
case errors::Unresolved_Operator:
|
case errors::Unresolved_Operator:
|
||||||
return os << err.message << '\n';
|
return os << err.message << '\n';
|
||||||
@ -173,6 +174,15 @@ Error errors::expected_keyword(Token const& unexpected, std::string_view keyword
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Error errors::not_callable(std::optional<Location> location, Value::Type value_type)
|
||||||
|
{
|
||||||
|
Error err;
|
||||||
|
err.type = errors::Not_Callable;
|
||||||
|
err.location = std::move(location);
|
||||||
|
err.message = format("Couldn't call value of type ", type_name(value_type));
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
void errors::all_tokens_were_not_parsed(std::span<Token> tokens)
|
void errors::all_tokens_were_not_parsed(std::span<Token> tokens)
|
||||||
{
|
{
|
||||||
error_heading(std::cerr, std::nullopt, Error_Level::Bug);
|
error_heading(std::cerr, std::nullopt, Error_Level::Bug);
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
static auto numeric_binary_operator(auto binop)
|
static auto numeric_binary_operator(auto binop)
|
||||||
{
|
{
|
||||||
return [binop = std::move(binop)](std::vector<Value> args) {
|
return [binop = std::move(binop)](std::vector<Value> args) -> Result<Value> {
|
||||||
auto result = std::move(args.front());
|
auto result = std::move(args.front());
|
||||||
for (auto &v : std::span(args).subspan(1)) {
|
for (auto &v : std::span(args).subspan(1)) {
|
||||||
assert(result.type == Value::Type::Number, "LHS should be a number");
|
assert(result.type == Value::Type::Number, "LHS should be a number");
|
||||||
@ -20,20 +20,26 @@ Interpreter::Interpreter()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Interpreter::~Interpreter()
|
||||||
|
{
|
||||||
|
Env::pool = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
Interpreter::Interpreter(std::ostream& out)
|
Interpreter::Interpreter(std::ostream& out)
|
||||||
: out(out)
|
: out(out)
|
||||||
{
|
{
|
||||||
functions["typeof"] = [](std::vector<Value> args) -> Value {
|
assert(Env::pool == nullptr, "Only one instance of interpreter can be at one time");
|
||||||
assert(args.size() == 1, "typeof expects only one argument");
|
Env::pool = &env_pool;
|
||||||
switch (args.front().type) {
|
|
||||||
case Value::Type::Nil: return Value::symbol("nil");
|
|
||||||
case Value::Type::Number: return Value::symbol("number");
|
|
||||||
case Value::Type::Symbol: return Value::symbol("symbol");
|
|
||||||
}
|
|
||||||
unreachable();
|
|
||||||
};
|
|
||||||
|
|
||||||
functions["say"] = [&out](std::vector<Value> args) -> Value {
|
auto &global = env_pool.emplace_back();
|
||||||
|
global.parent_enviroment_id = 0;
|
||||||
|
|
||||||
|
global.force_define("typeof", [](std::vector<Value> args) -> Value {
|
||||||
|
assert(args.size() == 1, "typeof expects only one argument");
|
||||||
|
return Value::symbol(std::string(type_name(args.front().type)));
|
||||||
|
});
|
||||||
|
|
||||||
|
global.force_define("say", [&out](std::vector<Value> args) -> Value {
|
||||||
for (auto it = args.begin(); it != args.end(); ++it) {
|
for (auto it = args.begin(); it != args.end(); ++it) {
|
||||||
out << *it;
|
out << *it;
|
||||||
if (std::next(it) != args.end())
|
if (std::next(it) != args.end())
|
||||||
@ -41,7 +47,7 @@ Interpreter::Interpreter(std::ostream& out)
|
|||||||
}
|
}
|
||||||
out << '\n';
|
out << '\n';
|
||||||
return {};
|
return {};
|
||||||
};
|
});
|
||||||
|
|
||||||
operators["+"] = numeric_binary_operator(std::plus<>{});
|
operators["+"] = numeric_binary_operator(std::plus<>{});
|
||||||
operators["-"] = numeric_binary_operator(std::minus<>{});
|
operators["-"] = numeric_binary_operator(std::minus<>{});
|
||||||
@ -49,6 +55,16 @@ Interpreter::Interpreter(std::ostream& out)
|
|||||||
operators["/"] = numeric_binary_operator(std::divides<>{});
|
operators["/"] = numeric_binary_operator(std::divides<>{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Env& Interpreter::env()
|
||||||
|
{
|
||||||
|
return env_pool[current_env];
|
||||||
|
}
|
||||||
|
|
||||||
|
Env const& Interpreter::env() const
|
||||||
|
{
|
||||||
|
return env_pool[current_env];
|
||||||
|
}
|
||||||
|
|
||||||
Result<Value> Interpreter::eval(Ast &&ast)
|
Result<Value> Interpreter::eval(Ast &&ast)
|
||||||
{
|
{
|
||||||
switch (ast.type) {
|
switch (ast.type) {
|
||||||
@ -84,15 +100,18 @@ Result<Value> Interpreter::eval(Ast &&ast)
|
|||||||
|
|
||||||
case Ast::Type::Call:
|
case Ast::Type::Call:
|
||||||
{
|
{
|
||||||
|
auto location = ast.arguments.front().location;
|
||||||
Value func = Try(eval(std::move(ast.arguments.front())));
|
Value func = Try(eval(std::move(ast.arguments.front())));
|
||||||
assert(func.type == Value::Type::Symbol, "Currently only symbols can be called");
|
if (func.type != Value::Type::Symbol)
|
||||||
if (auto it = functions.find(func.s); it != functions.end()) {
|
return errors::not_callable(std::move(location), func.type);
|
||||||
|
|
||||||
|
if (auto body = env().find(func.s); body) {
|
||||||
std::vector<Value> values;
|
std::vector<Value> values;
|
||||||
values.reserve(ast.arguments.size());
|
values.reserve(ast.arguments.size());
|
||||||
for (auto& a : std::span(ast.arguments).subspan(1)) {
|
for (auto& a : std::span(ast.arguments).subspan(1)) {
|
||||||
values.push_back(Try(eval(std::move(a))));
|
values.push_back(Try(eval(std::move(a))));
|
||||||
}
|
}
|
||||||
return it->second(std::move(values));
|
return (*body)(std::move(values));
|
||||||
} else {
|
} else {
|
||||||
return errors::function_not_defined(func);
|
return errors::function_not_defined(func);
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,7 @@ namespace errors
|
|||||||
Function_Not_Defined,
|
Function_Not_Defined,
|
||||||
Unresolved_Operator,
|
Unresolved_Operator,
|
||||||
Expected_Keyword,
|
Expected_Keyword,
|
||||||
|
Not_Callable
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,6 +114,15 @@ struct Error
|
|||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, Error const& err);
|
std::ostream& operator<<(std::ostream& os, Error const& err);
|
||||||
|
|
||||||
|
template<template<typename ...> typename Template, typename>
|
||||||
|
struct is_template : std::false_type {};
|
||||||
|
|
||||||
|
template<template<typename ...> typename Template, typename ...T>
|
||||||
|
struct is_template<Template, Template<T...>> : std::true_type {};
|
||||||
|
|
||||||
|
template<template<typename ...> typename Template, typename T>
|
||||||
|
constexpr auto is_template_v = is_template<Template, T>::value;
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
struct [[nodiscard("This value may contain critical error, so it should NOT be ignored")]] Result : tl::expected<T, Error>
|
struct [[nodiscard("This value may contain critical error, so it should NOT be ignored")]] Result : tl::expected<T, Error>
|
||||||
{
|
{
|
||||||
@ -149,6 +159,23 @@ struct [[nodiscard("This value may contain critical error, so it should NOT be i
|
|||||||
return Storage::value();
|
return Storage::value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline tl::expected<T, Error> to_expected() &&
|
||||||
|
{
|
||||||
|
return *static_cast<Storage*>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Map>
|
||||||
|
requires is_template_v<Result, std::invoke_result_t<Map, T&&>>
|
||||||
|
auto and_then(Map &&map) &&
|
||||||
|
{
|
||||||
|
return std::move(*static_cast<Storage*>(this)).and_then(
|
||||||
|
[map = std::forward<Map>(map)](T &&value) {
|
||||||
|
return std::move(map)(std::move(value)).to_expected();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
using Storage::and_then;
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOTE This implementation requires C++ language extension: statement expressions
|
// NOTE This implementation requires C++ language extension: statement expressions
|
||||||
@ -390,22 +417,54 @@ struct Number
|
|||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, Number const& num);
|
std::ostream& operator<<(std::ostream& os, Number const& num);
|
||||||
|
|
||||||
|
struct Value;
|
||||||
|
|
||||||
|
using Function = std::function<Result<Value>(std::vector<Value>)>;
|
||||||
|
|
||||||
|
template<typename T, typename ...XS>
|
||||||
|
constexpr auto is_one_of = (std::is_same_v<T, XS> || ...);
|
||||||
|
|
||||||
// TODO Add location
|
// TODO Add location
|
||||||
struct Value
|
struct Value
|
||||||
{
|
{
|
||||||
static Result<Value> from(Token t);
|
static Result<Value> from(Token t);
|
||||||
static Value number(Number n);
|
static Value number(Number n);
|
||||||
static Value symbol(std::string s);
|
static Value symbol(std::string s);
|
||||||
|
static Value lambda(Function f);
|
||||||
|
|
||||||
enum class Type
|
enum class Type
|
||||||
{
|
{
|
||||||
Nil,
|
Nil,
|
||||||
Number,
|
Number,
|
||||||
Symbol,
|
Symbol,
|
||||||
|
Lambda,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Value() = default;
|
||||||
|
Value(Value const&) = default;
|
||||||
|
Value(Value &&) = default;
|
||||||
|
Value& operator=(Value const&) = default;
|
||||||
|
Value& operator=(Value &&) = default;
|
||||||
|
|
||||||
|
template<typename Callable>
|
||||||
|
requires (!std::is_same_v<std::remove_cvref_t<Callable>, Value>)
|
||||||
|
&& std::invocable<Callable, std::vector<Value>>
|
||||||
|
&& is_one_of<std::invoke_result_t<Callable, std::vector<Value>>, Value, Result<Value>>
|
||||||
|
inline Value(Callable &&callable)
|
||||||
|
: type{Type::Lambda}
|
||||||
|
{
|
||||||
|
if constexpr (std::is_same_v<Result<Value>, std::invoke_result_t<Callable, std::vector<Value>>>) {
|
||||||
|
f = std::move(callable);
|
||||||
|
} else {
|
||||||
|
f = [fun = std::move(callable)](std::vector<Value> args) -> Result<Value> {
|
||||||
|
return fun(std::move(args));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Type type = Type::Nil;
|
Type type = Type::Nil;
|
||||||
Number n{};
|
Number n{};
|
||||||
|
Function f{};
|
||||||
|
|
||||||
// TODO Most strings should not be allocated by Value, but reference to string allocated previously
|
// TODO Most strings should not be allocated by Value, but reference to string allocated previously
|
||||||
// Wrapper for std::string is needed that will allocate only when needed, middle ground between:
|
// Wrapper for std::string is needed that will allocate only when needed, middle ground between:
|
||||||
@ -413,24 +472,56 @@ struct Value
|
|||||||
// std::string_view - not-owning string type
|
// std::string_view - not-owning string type
|
||||||
std::string s{};
|
std::string s{};
|
||||||
|
|
||||||
|
Result<Value> operator()(std::vector<Value> args);
|
||||||
|
|
||||||
bool operator==(Value const& other) const;
|
bool operator==(Value const& other) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::string_view type_name(Value::Type t);
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, Value const& v);
|
std::ostream& operator<<(std::ostream& os, Value const& v);
|
||||||
|
|
||||||
using Function = std::function<Value(std::vector<Value>)>;
|
struct Env
|
||||||
|
{
|
||||||
|
static std::vector<Env> *pool;
|
||||||
|
std::unordered_map<std::string, Value> variables;
|
||||||
|
usize parent_enviroment_id;
|
||||||
|
|
||||||
|
Env() = default;
|
||||||
|
Env(Env const&) = delete;
|
||||||
|
Env(Env &&) = default;
|
||||||
|
Env& operator=(Env const&) = delete;
|
||||||
|
Env& operator=(Env &&) = default;
|
||||||
|
|
||||||
|
static Env& global();
|
||||||
|
|
||||||
|
/// Defines new variable regardless of it's current existance
|
||||||
|
Env& force_define(std::string name, Value new_value);
|
||||||
|
Env& parent();
|
||||||
|
Value* find(std::string const& name);
|
||||||
|
|
||||||
|
usize operator++() const;
|
||||||
|
usize operator--();
|
||||||
|
|
||||||
|
static constexpr decltype(Env::parent_enviroment_id) Unused = -1;
|
||||||
|
};
|
||||||
|
|
||||||
struct Interpreter
|
struct Interpreter
|
||||||
{
|
{
|
||||||
std::ostream &out;
|
std::ostream &out;
|
||||||
std::unordered_map<std::string, Function> functions;
|
|
||||||
std::unordered_map<std::string, Function> operators;
|
std::unordered_map<std::string, Function> operators;
|
||||||
|
std::vector<Env> env_pool;
|
||||||
|
usize current_env = 0;
|
||||||
|
|
||||||
Interpreter();
|
Interpreter();
|
||||||
|
~Interpreter();
|
||||||
Interpreter(std::ostream& out);
|
Interpreter(std::ostream& out);
|
||||||
Interpreter(Interpreter const&) = delete;
|
Interpreter(Interpreter const&) = delete;
|
||||||
Interpreter(Interpreter &&) = default;
|
Interpreter(Interpreter &&) = default;
|
||||||
|
|
||||||
|
Env& env();
|
||||||
|
Env const& env() const;
|
||||||
|
|
||||||
Result<Value> eval(Ast &&ast);
|
Result<Value> eval(Ast &&ast);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -449,6 +540,8 @@ namespace errors
|
|||||||
Error unresolved_operator(Token const& op);
|
Error unresolved_operator(Token const& op);
|
||||||
Error expected_keyword(Token const& unexpected, std::string_view keyword);
|
Error expected_keyword(Token const& unexpected, std::string_view keyword);
|
||||||
|
|
||||||
|
Error not_callable(std::optional<Location> location, Value::Type value_type);
|
||||||
|
|
||||||
[[noreturn]]
|
[[noreturn]]
|
||||||
void all_tokens_were_not_parsed(std::span<Token>);
|
void all_tokens_were_not_parsed(std::span<Token>);
|
||||||
}
|
}
|
||||||
|
@ -110,23 +110,23 @@ Result<Ast> Parser::parse_atomic_expression()
|
|||||||
token_id = start;
|
token_id = start;
|
||||||
}
|
}
|
||||||
|
|
||||||
return parse_sequence().and_then([&](Ast ast) -> tl::expected<Ast, Error> {
|
return parse_sequence().and_then([&](Ast &&ast) -> Result<Ast> {
|
||||||
Try(ensure(Token::Type::Close_Block));
|
Try(ensure(Token::Type::Close_Block));
|
||||||
consume();
|
consume();
|
||||||
return Ast::block(opening.location, ast, std::move(parameters));
|
return Ast::block(opening.location, std::move(ast), std::move(parameters));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
case Token::Type::Open_Paren:
|
case Token::Type::Open_Paren:
|
||||||
consume();
|
consume();
|
||||||
return parse_expression().and_then([&](Ast ast) -> tl::expected<Ast, Error> {
|
return parse_expression().and_then([&](Ast ast) -> Result<Ast> {
|
||||||
Try(ensure(Token::Type::Close_Paren));
|
Try(ensure(Token::Type::Close_Paren));
|
||||||
consume();
|
consume();
|
||||||
return ast;
|
return ast;
|
||||||
});
|
});
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return peek().and_then([](auto const& token) -> tl::expected<Ast, Error> {
|
return peek().and_then([](auto const& token) -> Result<Ast> {
|
||||||
return tl::unexpected(errors::unexpected_token(token));
|
return tl::unexpected(errors::unexpected_token(token));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
80
src/tests/environment.cc
Normal file
80
src/tests/environment.cc
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
#include <boost/ut.hpp>
|
||||||
|
#include <musique.hh>
|
||||||
|
|
||||||
|
using namespace boost::ut;
|
||||||
|
using namespace std::string_view_literals;
|
||||||
|
|
||||||
|
static void equals(
|
||||||
|
Value *received,
|
||||||
|
Value expected,
|
||||||
|
reflection::source_location sl = reflection::source_location::current())
|
||||||
|
{
|
||||||
|
expect(received != nullptr, sl) << "Value was not found";
|
||||||
|
expect(eq(*received, expected), sl);
|
||||||
|
}
|
||||||
|
|
||||||
|
suite environment_test = [] {
|
||||||
|
"Global enviroment exists"_test = [] {
|
||||||
|
Interpreter i;
|
||||||
|
expect(eq(&i.env_pool.front(), &Env::global()));
|
||||||
|
};
|
||||||
|
|
||||||
|
"Environment scoping"_test = [] {
|
||||||
|
should("nested scoping preserve outer scope") = [] {
|
||||||
|
Interpreter i;
|
||||||
|
|
||||||
|
i.env().force_define("x", Value::number(Number(10)));
|
||||||
|
i.env().force_define("y", Value::number(Number(20)));
|
||||||
|
|
||||||
|
equals(i.env().find("x"), Value::number(Number(10)));
|
||||||
|
equals(i.env().find("y"), Value::number(Number(20)));
|
||||||
|
|
||||||
|
i.current_env = ++i.env();
|
||||||
|
i.env().force_define("x", Value::number(Number(30)));
|
||||||
|
equals(i.env().find("x"), Value::number(Number(30)));
|
||||||
|
equals(i.env().find("y"), Value::number(Number(20)));
|
||||||
|
|
||||||
|
i.current_env = --i.env();
|
||||||
|
equals(i.env().find("x"), Value::number(Number(10)));
|
||||||
|
equals(i.env().find("y"), Value::number(Number(20)));
|
||||||
|
};
|
||||||
|
|
||||||
|
should("nested variables missing from outer scope") = [] {
|
||||||
|
Interpreter i;
|
||||||
|
|
||||||
|
i.current_env = ++i.env();
|
||||||
|
i.env().force_define("x", Value::number(Number(30)));
|
||||||
|
equals(i.env().find("x"), Value::number(Number(30)));
|
||||||
|
|
||||||
|
i.current_env = --i.env();
|
||||||
|
expect(eq(i.env().find("x"), nullptr));
|
||||||
|
};
|
||||||
|
|
||||||
|
should("stay at global scope when too much end of scope is applied") = [] {
|
||||||
|
Interpreter i;
|
||||||
|
i.current_env = --i.env();
|
||||||
|
expect(eq(&i.env(), &Env::global()));
|
||||||
|
};
|
||||||
|
|
||||||
|
should("reuse already allocated enviroments") = [] {
|
||||||
|
Interpreter i;
|
||||||
|
|
||||||
|
i.current_env = ++i.env();
|
||||||
|
auto const first_env = &i.env();
|
||||||
|
i.env().force_define("x", Value::number(Number(30)));
|
||||||
|
equals(i.env().find("x"), Value::number(Number(30)));
|
||||||
|
|
||||||
|
i.current_env = --i.env();
|
||||||
|
expect(eq(i.env().find("x"), nullptr));
|
||||||
|
|
||||||
|
i.current_env = ++i.env();
|
||||||
|
expect(eq(first_env, &i.env()));
|
||||||
|
expect(eq(i.env().find("x"), nullptr));
|
||||||
|
i.env().force_define("x", Value::number(Number(30)));
|
||||||
|
equals(i.env().find("x"), Value::number(Number(30)));
|
||||||
|
|
||||||
|
i.current_env = --i.env();
|
||||||
|
expect(eq(i.env().find("x"), nullptr));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
@ -59,5 +59,20 @@ suite intepreter_test = [] {
|
|||||||
produces_output("say 1; say 2; say 3", "1\n2\n3\n");
|
produces_output("say 1; say 2; say 3", "1\n2\n3\n");
|
||||||
produces_output("say 1 2 3", "1 2 3\n");
|
produces_output("say 1 2 3", "1 2 3\n");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
should("allows only for calling which is callable") = [] {
|
||||||
|
Interpreter i;
|
||||||
|
{
|
||||||
|
auto result = Parser::parse("10 20", "test").and_then([&](Ast &&ast) { return i.eval(std::move(ast)); });
|
||||||
|
expect(!result.has_value()) << "Expected code to have failed";
|
||||||
|
expect(eq(result.error().type, errors::Not_Callable));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
i.env().force_define("call_me", Value::number(Number(10)));
|
||||||
|
auto result = Parser::parse("call_me 20", "test").and_then([&](Ast &&ast) { return i.eval(std::move(ast)); });
|
||||||
|
expect(!result.has_value()) << "Expected code to have failed";
|
||||||
|
expect(eq(result.error().type, errors::Not_Callable));
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
35
src/tests/value.cc
Normal file
35
src/tests/value.cc
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#include <boost/ut.hpp>
|
||||||
|
#include <musique.hh>
|
||||||
|
|
||||||
|
using namespace boost::ut;
|
||||||
|
using namespace std::string_view_literals;
|
||||||
|
|
||||||
|
static std::string str(auto const& printable)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << printable;
|
||||||
|
return std::move(ss).str();
|
||||||
|
}
|
||||||
|
|
||||||
|
suite value_test = [] {
|
||||||
|
"Comparisons"_test = [] {
|
||||||
|
should("be always not equal for lambdas") = [] {
|
||||||
|
expect(neq(Value::lambda(nullptr), Value::lambda(nullptr)));
|
||||||
|
};
|
||||||
|
|
||||||
|
should("are always not equal when types differ") = [] {
|
||||||
|
expect(neq(Value::symbol("0"), Value::number(Number(0))));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
"Value printing"_test = [] {
|
||||||
|
expect(eq("nil"sv, str(Value{})));
|
||||||
|
|
||||||
|
expect(eq("10"sv, str(Value::number(Number(10)))));
|
||||||
|
expect(eq("1/2"sv, str(Value::number(Number(2, 4)))));
|
||||||
|
|
||||||
|
expect(eq("foo"sv, str(Value::symbol("foo"))));
|
||||||
|
|
||||||
|
expect(eq("<lambda>"sv, str(Value::lambda(nullptr))));
|
||||||
|
};
|
||||||
|
};
|
36
src/value.cc
36
src/value.cc
@ -20,7 +20,7 @@ Value Value::number(Number n)
|
|||||||
{
|
{
|
||||||
Value v;
|
Value v;
|
||||||
v.type = Type::Number;
|
v.type = Type::Number;
|
||||||
v.n = n;
|
v.n = std::move(n).simplify();
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,10 +28,27 @@ Value Value::symbol(std::string s)
|
|||||||
{
|
{
|
||||||
Value v;
|
Value v;
|
||||||
v.type = Type::Symbol;
|
v.type = Type::Symbol;
|
||||||
v.s = s;
|
v.s = std::move(s);
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Value Value::lambda(Function f)
|
||||||
|
{
|
||||||
|
Value v;
|
||||||
|
v.type = Type::Lambda;
|
||||||
|
v.f = std::move(f);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<Value> Value::operator()(std::vector<Value> args)
|
||||||
|
{
|
||||||
|
if (type == Type::Lambda) {
|
||||||
|
return f(std::move(args));
|
||||||
|
}
|
||||||
|
// TODO Fill location
|
||||||
|
return errors::not_callable(std::nullopt, type);
|
||||||
|
}
|
||||||
|
|
||||||
bool Value::operator==(Value const& other) const
|
bool Value::operator==(Value const& other) const
|
||||||
{
|
{
|
||||||
if (type != other.type)
|
if (type != other.type)
|
||||||
@ -41,6 +58,7 @@ bool Value::operator==(Value const& other) const
|
|||||||
case Type::Nil: return true;
|
case Type::Nil: return true;
|
||||||
case Type::Number: return n == other.n;
|
case Type::Number: return n == other.n;
|
||||||
case Type::Symbol: return s == other.s;
|
case Type::Symbol: return s == other.s;
|
||||||
|
case Type::Lambda: return false; // TODO Reconsider if functions are comparable
|
||||||
}
|
}
|
||||||
|
|
||||||
unreachable();
|
unreachable();
|
||||||
@ -57,6 +75,20 @@ std::ostream& operator<<(std::ostream& os, Value const& v)
|
|||||||
|
|
||||||
case Value::Type::Symbol:
|
case Value::Type::Symbol:
|
||||||
return os << v.s;
|
return os << v.s;
|
||||||
|
|
||||||
|
case Value::Type::Lambda:
|
||||||
|
return os << "<lambda>";
|
||||||
|
}
|
||||||
|
unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view type_name(Value::Type t)
|
||||||
|
{
|
||||||
|
switch (t) {
|
||||||
|
case Value::Type::Lambda: return "lambda";
|
||||||
|
case Value::Type::Nil: return "nil";
|
||||||
|
case Value::Type::Number: return "number";
|
||||||
|
case Value::Type::Symbol: return "symbol";
|
||||||
}
|
}
|
||||||
unreachable();
|
unreachable();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user