Scoped variables, single namespace variables and functions

This commit is contained in:
Robert Bendun 2022-05-17 02:35:51 +02:00
parent 0f9c46dffb
commit 5d51d1672f
11 changed files with 359 additions and 23 deletions

View File

@ -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
View File

@ -0,0 +1,2 @@
var x = 10;
say (x + 1)

49
src/environment.cc Normal file
View 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];
}

View File

@ -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);

View File

@ -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);
} }

View File

@ -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>);
} }

View File

@ -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
View 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));
};
};
};

View File

@ -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
View 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))));
};
};

View File

@ -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();
} }