Compare commits
3 Commits
main
...
enviroment
Author | SHA1 | Date | |
---|---|---|---|
db932b94a9 | |||
da6d57a280 | |||
c55650e12b |
6
Makefile
6
Makefile
@ -19,7 +19,7 @@ Obj= \
|
|||||||
Release_Obj=$(addprefix bin/,$(Obj))
|
Release_Obj=$(addprefix bin/,$(Obj))
|
||||||
Debug_Obj=$(addprefix bin/debug/,$(Obj))
|
Debug_Obj=$(addprefix bin/debug/,$(Obj))
|
||||||
|
|
||||||
all: bin/musique bin/unit-tests
|
all: bin/musique
|
||||||
|
|
||||||
debug: bin/debug/musique
|
debug: bin/debug/musique
|
||||||
|
|
||||||
@ -60,8 +60,8 @@ doc: Doxyfile src/*.cc src/*.hh
|
|||||||
doc-open: doc
|
doc-open: doc
|
||||||
xdg-open ./doc/build/html/index.html
|
xdg-open ./doc/build/html/index.html
|
||||||
|
|
||||||
bin/unit-tests: src/tests/*.cc $(Release_Obj)
|
bin/unit-tests: src/tests/*.cc $(Debug_Obj)
|
||||||
g++ $(CXXFLAGS) $(CPPFLAGS) -o $@ $^
|
g++ $(CXXFLAGS) $(CPPFLAGS) $(DEBUG_FLAGS) -o $@ $^
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf bin coverage
|
rm -rf bin coverage
|
||||||
|
38
examples/church.mq
Normal file
38
examples/church.mq
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
-- PAIR definition
|
||||||
|
var pair = [x y | [z | z x y]];
|
||||||
|
var car = [p | p [x y | x]];
|
||||||
|
var cdr = [p | p [x y | y]];
|
||||||
|
|
||||||
|
var x = pair 100 200;
|
||||||
|
say (car x);
|
||||||
|
say (cdr x);
|
||||||
|
|
||||||
|
-- This two values should be builtin, but currently is not
|
||||||
|
var true = (1 == 1);
|
||||||
|
var false = (1 != 1);
|
||||||
|
|
||||||
|
-- LIST definition
|
||||||
|
var null = pair true true;
|
||||||
|
var is_empty = car;
|
||||||
|
var head = [list | car (cdr list)];
|
||||||
|
var tail = [list | cdr (cdr list)];
|
||||||
|
var cons = [head tail | pair false (pair head tail)];
|
||||||
|
|
||||||
|
var for_each = [list iterator |
|
||||||
|
if (is_empty list) [| nil ] [|
|
||||||
|
iterator (head list);
|
||||||
|
for_each (tail list) iterator ]];
|
||||||
|
|
||||||
|
var map = [list iterator |
|
||||||
|
if (is_empty list) [| null ] [|
|
||||||
|
cons (iterator (head list)) (map (tail list) iterator) ]];
|
||||||
|
|
||||||
|
var foldr = [list init folder |
|
||||||
|
if (is_empty list)
|
||||||
|
[| init ]
|
||||||
|
[| foldr (tail list) (folder (head list) init) folder ]];
|
||||||
|
|
||||||
|
var range = [start stop | if (start >= stop) [|cons start null] [|cons start (range (start+1) stop)]];
|
||||||
|
|
||||||
|
var xs = range 1 5;
|
||||||
|
say (foldr xs 1 [x y|x*y]);
|
@ -2,7 +2,14 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
std::vector<Env> *Env::pool = nullptr;
|
std::shared_ptr<Env> Env::global = nullptr;
|
||||||
|
|
||||||
|
std::shared_ptr<Env> Env::make()
|
||||||
|
{
|
||||||
|
auto new_env = new Env();
|
||||||
|
assert(new_env, "Cannot construct new env");
|
||||||
|
return std::shared_ptr<Env>(new_env);
|
||||||
|
}
|
||||||
|
|
||||||
Env& Env::force_define(std::string name, Value new_value)
|
Env& Env::force_define(std::string name, Value new_value)
|
||||||
{
|
{
|
||||||
@ -10,14 +17,9 @@ Env& Env::force_define(std::string name, Value new_value)
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Env& Env::parent()
|
|
||||||
{
|
|
||||||
return (*pool)[parent_enviroment_id];
|
|
||||||
}
|
|
||||||
|
|
||||||
Value* Env::find(std::string const& name)
|
Value* Env::find(std::string const& name)
|
||||||
{
|
{
|
||||||
for (Env *prev = nullptr, *env = this; env != prev; prev = std::exchange(env, &env->parent())) {
|
for (Env *env = this; env; env = env->parent.get()) {
|
||||||
if (auto it = env->variables.find(name); it != env->variables.end()) {
|
if (auto it = env->variables.find(name); it != env->variables.end()) {
|
||||||
return &it->second;
|
return &it->second;
|
||||||
}
|
}
|
||||||
@ -25,29 +27,14 @@ Value* Env::find(std::string const& name)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
usize Env::operator++() const
|
std::shared_ptr<Env> Env::enter()
|
||||||
{
|
{
|
||||||
std::cerr << "ENTER SCOPE" << std::endl;
|
auto next = make();
|
||||||
auto const parent_id = this - pool->data();
|
next->parent = shared_from_this();
|
||||||
auto const free = std::find_if(pool->begin(), pool->end(), [](Env const& env) { return env.parent_enviroment_id == Env::Unused; });
|
return next;
|
||||||
Env* next = free == pool->end()
|
|
||||||
? &pool->emplace_back()
|
|
||||||
: &*free;
|
|
||||||
|
|
||||||
next->parent_enviroment_id = parent_id;
|
|
||||||
return next - pool->data();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
usize Env::operator--()
|
std::shared_ptr<Env> Env::leave()
|
||||||
{
|
{
|
||||||
std::cerr << "LEAVE SCOPE" << std::endl;
|
return parent;
|
||||||
if (this == pool->data())
|
|
||||||
return 0;
|
|
||||||
variables.clear();
|
|
||||||
return std::exchange(parent_enviroment_id, Unused);
|
|
||||||
}
|
|
||||||
|
|
||||||
Env& Env::global()
|
|
||||||
{
|
|
||||||
return (*pool)[0];
|
|
||||||
}
|
}
|
||||||
|
@ -27,17 +27,16 @@ Interpreter::Interpreter()
|
|||||||
|
|
||||||
Interpreter::~Interpreter()
|
Interpreter::~Interpreter()
|
||||||
{
|
{
|
||||||
Env::pool = nullptr;
|
Env::global.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
Interpreter::Interpreter(std::ostream& out)
|
Interpreter::Interpreter(std::ostream& out)
|
||||||
: out(out)
|
: out(out)
|
||||||
{
|
{
|
||||||
assert(Env::pool == nullptr, "Only one instance of interpreter can be at one time");
|
assert(!bool(Env::global), "Only one instance of interpreter can be at one time");
|
||||||
Env::pool = &env_pool;
|
|
||||||
|
|
||||||
auto &global = env_pool.emplace_back();
|
env = Env::global = Env::make();
|
||||||
global.parent_enviroment_id = 0;
|
auto &global = *Env::global;
|
||||||
|
|
||||||
global.force_define("typeof", [](Interpreter&, std::vector<Value> args) -> Value {
|
global.force_define("typeof", [](Interpreter&, std::vector<Value> args) -> Value {
|
||||||
assert(args.size() == 1, "typeof expects only one argument");
|
assert(args.size() == 1, "typeof expects only one argument");
|
||||||
@ -78,16 +77,6 @@ Interpreter::Interpreter(std::ostream& out)
|
|||||||
operators["!="] = binary_operator(std::not_equal_to<>{});
|
operators["!="] = binary_operator(std::not_equal_to<>{});
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
@ -95,7 +84,7 @@ Result<Value> Interpreter::eval(Ast &&ast)
|
|||||||
switch (ast.token.type) {
|
switch (ast.token.type) {
|
||||||
case Token::Type::Symbol:
|
case Token::Type::Symbol:
|
||||||
if (ast.token.source != "nil") {
|
if (ast.token.source != "nil") {
|
||||||
auto const value = env().find(std::string(ast.token.source));
|
auto const value = env->find(std::string(ast.token.source));
|
||||||
assert(value, "Missing variable error is not implemented yet: variable: "s + std::string(ast.token.source));
|
assert(value, "Missing variable error is not implemented yet: variable: "s + std::string(ast.token.source));
|
||||||
return *value;
|
return *value;
|
||||||
}
|
}
|
||||||
@ -149,7 +138,7 @@ Result<Value> Interpreter::eval(Ast &&ast)
|
|||||||
assert(ast.arguments.size() == 2, "Only simple assigments are supported now");
|
assert(ast.arguments.size() == 2, "Only simple assigments are supported now");
|
||||||
assert(ast.arguments.front().type == Ast::Type::Literal, "Only names are supported as LHS arguments now");
|
assert(ast.arguments.front().type == Ast::Type::Literal, "Only names are supported as LHS arguments now");
|
||||||
assert(ast.arguments.front().token.type == Token::Type::Symbol, "Only names are supported as LHS arguments now");
|
assert(ast.arguments.front().token.type == Token::Type::Symbol, "Only names are supported as LHS arguments now");
|
||||||
env().force_define(std::string(ast.arguments.front().token.source), Try(eval(std::move(ast.arguments.back()))));
|
env->force_define(std::string(ast.arguments.front().token.source), Try(eval(std::move(ast.arguments.back()))));
|
||||||
return Value{};
|
return Value{};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,6 +151,8 @@ Result<Value> Interpreter::eval(Ast &&ast)
|
|||||||
assert(param.type == Ast::Type::Literal && param.token.type == Token::Type::Symbol, "Not a name in parameter section of Ast::lambda");
|
assert(param.type == Ast::Type::Literal && param.token.type == Token::Type::Symbol, "Not a name in parameter section of Ast::lambda");
|
||||||
lambda.parameters.push_back(std::string(std::move(param).token.source));
|
lambda.parameters.push_back(std::string(std::move(param).token.source));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lambda.context = env;
|
||||||
lambda.body = std::move(ast.arguments.back());
|
lambda.body = std::move(ast.arguments.back());
|
||||||
return Value::lambda(std::move(lambda));
|
return Value::lambda(std::move(lambda));
|
||||||
}
|
}
|
||||||
@ -170,3 +161,13 @@ Result<Value> Interpreter::eval(Ast &&ast)
|
|||||||
unimplemented();
|
unimplemented();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void Interpreter::enter_scope()
|
||||||
|
{
|
||||||
|
env = env->enter();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::leave_scope()
|
||||||
|
{
|
||||||
|
assert(env != Env::global, "Cannot leave global scope");
|
||||||
|
env = env->leave();
|
||||||
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#include <concepts>
|
#include <concepts>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <span>
|
#include <span>
|
||||||
@ -422,8 +423,9 @@ struct Number
|
|||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, Number const& num);
|
std::ostream& operator<<(std::ostream& os, Number const& num);
|
||||||
|
|
||||||
struct Value;
|
struct Env;
|
||||||
struct Interpreter;
|
struct Interpreter;
|
||||||
|
struct Value;
|
||||||
|
|
||||||
using Function = std::function<Result<Value>(Interpreter &i, std::vector<Value>)>;
|
using Function = std::function<Result<Value>(Interpreter &i, std::vector<Value>)>;
|
||||||
|
|
||||||
@ -432,6 +434,7 @@ struct Lambda
|
|||||||
Location location;
|
Location location;
|
||||||
std::vector<std::string> parameters;
|
std::vector<std::string> parameters;
|
||||||
Ast body;
|
Ast body;
|
||||||
|
std::shared_ptr<Env> context;
|
||||||
|
|
||||||
Result<Value> operator()(Interpreter &i, std::vector<Value> params);
|
Result<Value> operator()(Interpreter &i, std::vector<Value> params);
|
||||||
};
|
};
|
||||||
@ -501,37 +504,38 @@ 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);
|
||||||
|
|
||||||
struct Env
|
struct Env : std::enable_shared_from_this<Env>
|
||||||
{
|
{
|
||||||
static std::vector<Env> *pool;
|
// Constructor of Env class
|
||||||
std::unordered_map<std::string, Value> variables;
|
static std::shared_ptr<Env> make();
|
||||||
usize parent_enviroment_id;
|
|
||||||
|
static std::shared_ptr<Env> global;
|
||||||
|
std::unordered_map<std::string, Value> variables;
|
||||||
|
std::shared_ptr<Env> parent;
|
||||||
|
|
||||||
Env() = default;
|
|
||||||
Env(Env const&) = delete;
|
Env(Env const&) = delete;
|
||||||
Env(Env &&) = default;
|
Env(Env &&) = default;
|
||||||
Env& operator=(Env const&) = delete;
|
Env& operator=(Env const&) = delete;
|
||||||
Env& operator=(Env &&) = default;
|
Env& operator=(Env &&) = default;
|
||||||
|
|
||||||
static Env& global();
|
|
||||||
|
|
||||||
/// Defines new variable regardless of it's current existance
|
/// Defines new variable regardless of it's current existance
|
||||||
Env& force_define(std::string name, Value new_value);
|
Env& force_define(std::string name, Value new_value);
|
||||||
Env& parent();
|
|
||||||
Value* find(std::string const& name);
|
Value* find(std::string const& name);
|
||||||
|
|
||||||
usize operator++() const;
|
// Scope menagment
|
||||||
usize operator--();
|
std::shared_ptr<Env> enter();
|
||||||
|
std::shared_ptr<Env> leave();
|
||||||
|
|
||||||
static constexpr decltype(Env::parent_enviroment_id) Unused = -1;
|
private:
|
||||||
|
// Ensure that all values of this class are behind shared_ptr
|
||||||
|
Env() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Interpreter
|
struct Interpreter
|
||||||
{
|
{
|
||||||
std::ostream &out;
|
std::ostream &out;
|
||||||
std::unordered_map<std::string, Function> operators;
|
std::unordered_map<std::string, Function> operators;
|
||||||
std::vector<Env> env_pool;
|
std::shared_ptr<Env> env;
|
||||||
usize current_env = 0;
|
|
||||||
|
|
||||||
Interpreter();
|
Interpreter();
|
||||||
~Interpreter();
|
~Interpreter();
|
||||||
@ -539,10 +543,11 @@ struct Interpreter
|
|||||||
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);
|
||||||
|
|
||||||
|
// Scope managment
|
||||||
|
void enter_scope();
|
||||||
|
void leave_scope();
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace errors
|
namespace errors
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
#if 0
|
||||||
#include <boost/ut.hpp>
|
#include <boost/ut.hpp>
|
||||||
#include <musique.hh>
|
#include <musique.hh>
|
||||||
|
|
||||||
@ -78,3 +79,4 @@ suite environment_test = [] {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
@ -68,11 +68,17 @@ suite intepreter_test = [] {
|
|||||||
expect(eq(result.error().type, errors::Not_Callable));
|
expect(eq(result.error().type, errors::Not_Callable));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
i.env().force_define("call_me", Value::number(Number(10)));
|
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)); });
|
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(!result.has_value()) << "Expected code to have failed";
|
||||||
expect(eq(result.error().type, errors::Not_Callable));
|
expect(eq(result.error().type, errors::Not_Callable));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Added to explicitly test against bug that was in old implementation of enviroments.
|
||||||
|
// Previously this test would segfault
|
||||||
|
should("allow assigning result of function calls to a variable") = [] {
|
||||||
|
evaluates_to(Value::number(Number(42)), "var x = [i|i] 42; x");
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -129,16 +129,17 @@ std::string_view type_name(Value::Type t)
|
|||||||
|
|
||||||
Result<Value> Lambda::operator()(Interpreter &i, std::vector<Value> arguments)
|
Result<Value> Lambda::operator()(Interpreter &i, std::vector<Value> arguments)
|
||||||
{
|
{
|
||||||
i.current_env = ++i.env();
|
auto old_scope = std::exchange(i.env, context);
|
||||||
|
i.enter_scope();
|
||||||
|
|
||||||
assert(parameters.size() == arguments.size(), "wrong number of arguments");
|
assert(parameters.size() == arguments.size(), "wrong number of arguments");
|
||||||
|
|
||||||
auto &env = i.env();
|
|
||||||
for (usize j = 0; j < parameters.size(); ++j) {
|
for (usize j = 0; j < parameters.size(); ++j) {
|
||||||
env.force_define(parameters[j], std::move(arguments[j]));
|
i.env->force_define(parameters[j], std::move(arguments[j]));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto result = i.eval((Ast)body);
|
auto result = i.eval((Ast)body);
|
||||||
|
|
||||||
i.current_env = --i.env();
|
i.env = old_scope;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user