diff --git a/CMakeLists.txt b/CMakeLists.txt index a1a1a0b..3910a28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,9 +26,18 @@ set (TRIESTE_ENABLE_TESTING OFF) FetchContent_MakeAvailable_ExcludeFromAll(trieste) set(CMAKE_CXX_STANDARD 20) -add_library(rt OBJECT src/rt/rt.cc) - -add_library(lang OBJECT src/lang/lang.cc src/lang/interpreter.cc) +add_library(rt OBJECT src/rt/rt.cc src/rt/ui/mermaid.cc) + +add_library( + lang OBJECT + src/lang/lang.cc + src/lang/interpreter.cc + src/lang/passes/parse.cc + src/lang/passes/grouping.cc + src/lang/passes/call_stmts.cc + src/lang/passes/flatten.cc + src/lang/passes/bytecode.cc +) target_link_libraries(lang PRIVATE trieste::trieste) add_executable(verona_dyn src/main.cc) diff --git a/src/api.h b/src/api.h deleted file mode 100644 index cea5fa9..0000000 --- a/src/api.h +++ /dev/null @@ -1,169 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "rt/rt.h" - -namespace api { -using namespace objects; - -class Reference; - -class Object { - friend class Reference; - friend void - mermaid(std::initializer_list> roots); - - DynObject *value; - - Object(DynObject *object) : value(object) {} - - void copy(DynObject *new_object) { - add_reference(get_frame(), new_object); - auto old_object = value; - value = new_object; - remove_reference(get_frame(), old_object); - } - - void move(DynObject *new_value) { - auto old_value = value; - value = new_value; - remove_reference(get_frame(), old_value); - } - -public: - Object() : value(nullptr) {} - - Object(Object &other) : value(other.value) { - add_reference(get_frame(), value); - } - - Object(Object &&other) : value(other.value) { other.value = nullptr; } - - Object &operator=(Object &other) { - copy(other.value); - return *this; - } - - Object &operator=(Object &&other) { - move(other.value); - return *this; - } - - Object &operator=(std::nullptr_t) { - move(nullptr); - return *this; - } - - Object &operator=(Reference &other); - Object &operator=(Reference &&other); - - static Object create(std::string name) { return make_object(name); } - - Reference operator[](std::string name); - - ~Object() { - remove_reference(get_frame(), value); - } - - void freeze() { objects::freeze(value); } - - void create_region() { objects::create_region(value); } -}; - -class Reference { - friend class Object; - std::string key; - DynObject *src; - - void copy(DynObject *new_object) { - add_reference(src, new_object); - auto old = objects::set(src, key, new_object); - remove_reference(src, old); - } - - DynObject *get() { return objects::get(src, key); } - - Reference(std::string name, DynObject *object) : key(name), src(object) {} - -public: - Reference &operator=(Object &other) { - copy(other.value); - return *this; - } - - Reference &operator=(Object &&other) { - auto old_value = objects::set(src, key, other.value); - move_reference(get_frame(), src, other.value); - remove_reference(src, old_value); - // move semantics. - other.value = nullptr; - return *this; - } - - Reference &operator=(Reference &other) { - copy(other.get()); - return *this; - } - - Reference &operator=(Reference &&other) { - copy(other.get()); - return *this; - } - - Reference &operator=(std::nullptr_t) { - auto old_value = objects::set(src, key, nullptr); - remove_reference(src, old_value); - return *this; - } - - Reference operator[](std::string name) { return {name, objects::get(src, key)}; } -}; - -Reference Object::operator[](std::string name) { - return Reference(name, value); -} - -Object &Object::operator=(Reference &other) { - copy(other.get()); - return *this; -} - -Object &Object::operator=(Reference &&other) { - copy(other.get()); - return *this; -} - -struct UI : objects::UI -{ - void output(std::vector &edges, std::string message) { - std::ofstream out("mermaid.md"); - objects::mermaid(edges, out); - std::cout << message << std::endl; - std::cout << "Press a key!" << std::endl; - getchar(); - } -}; - -void mermaid(std::initializer_list> roots) { - UI ui; - std::vector edges; - for (auto &root : roots) { - edges.push_back( - {get_frame(), root.first, root.second.value}); - } - - ui.output(edges, ""); -} - -template void run(F &&f) { - UI ui; - size_t initial_count = objects::pre_run(); - f(); - objects::post_run(initial_count, ui); -}; -} // namespace api \ No newline at end of file diff --git a/src/lang/bytecode.h b/src/lang/bytecode.h index c1f9a77..c40b897 100644 --- a/src/lang/bytecode.h +++ b/src/lang/bytecode.h @@ -37,4 +37,3 @@ inline const trieste::TokenDef Jump{"jump", trieste::flag::print}; inline const trieste::TokenDef JumpFalse{"jump_false", trieste::flag::print}; inline const trieste::TokenDef Print("print", trieste::flag::print); inline const trieste::TokenDef IterNext("iter_next"); - diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index f28dbaf..7391383 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -1,393 +1,476 @@ -#include "bytecode.h" #include "../rt/rt.h" +#include "bytecode.h" #include "trieste/trieste.h" #include -#include #include #include +#include -namespace verona::interpreter { - -// ============================================== -// Public UI -// ============================================== -struct Bytecode { - trieste::Node body; -}; - -void delete_bytecode(Bytecode* bytecode) { - delete bytecode; -} - -// ============================================== -// Statement Effects -// ============================================== -struct ExecNext {}; -struct ExecJump { - trieste::Location target; -}; -struct ExecFunc { - trieste::Node body; - size_t arg_ctn; -}; -struct ExecReturn { - std::optional value; -}; - -// ============================================== -// Interpreter/state -// ============================================== -struct InterpreterFrame { - std::vector stack; - trieste::NodeIt ip; - trieste::Node body; - // Used for sanity checks - objects::DynObject *frame; -}; - -class Interpreter { - objects::UI* ui; - std::vector frame_stack; - // This is the top level stack - std::vector stack; - - objects::DynObject* pop(char const* data_info) { - auto v = stack.back(); - stack.pop_back(); - std::cout << "pop " << v << " (" << data_info << ")" << std::endl; - return v; - } - - std::variant run_stmt(trieste::Node& node) { - // ========================================== - // Operators that shouldn't be printed - // ========================================== - if (node == Print) { - // Console output - std::cout << node->location().view() << std::endl << std::endl; +namespace verona::interpreter +{ - // Mermaid output - std::vector edges{{nullptr, "?", objects::get_frame()}}; - ui->output(edges, std::string(node->location().view())); + // ============================================== + // Public UI + // ============================================== + struct Bytecode + { + trieste::Node body; + }; + + void delete_bytecode(Bytecode* bytecode) + { + delete bytecode; + } - // Continue - return ExecNext {}; - } - if (node == Label) { - return ExecNext {}; + // ============================================== + // Statement Effects + // ============================================== + struct ExecNext + {}; + struct ExecJump + { + trieste::Location target; + }; + struct ExecFunc + { + trieste::Node body; + size_t arg_ctn; + }; + struct ExecReturn + { + std::optional value; + }; + + // ============================================== + // Interpreter/state + // ============================================== + struct InterpreterFrame + { + trieste::NodeIt ip; + trieste::Node body; + rt::objects::DynObject* frame; + std::vector stack; + + ~InterpreterFrame() + { + rt::remove_reference(frame, frame); } + }; + + class Interpreter + { + rt::ui::UI* ui; + std::vector frame_stack; - // ========================================== - // Operators that should be printed - // ========================================== - std::cout << "Op: " << node->type().str() << std::endl; - if (node == CreateObject) + InterpreterFrame* push_stack_frame(trieste::Node body) { - objects::DynObject *obj = nullptr; - - assert(!node->empty() && "CreateObject has to specify the type of data"); - auto payload = node->at(0); - if (payload == Dictionary) { - obj = objects::make_object(); - } else if (payload == String) { - obj = objects::make_object(std::string(payload->location().view())); - } else if (payload == KeyIter) { - auto v = pop("iterator source"); - obj = objects::make_iter(v); - remove_reference(objects::get_frame(), v); - } else if (payload == Proto) { - obj = objects::make_object(); - // RC transferred - objects::set_prototype(obj, pop("prototype source")); - } else if (payload == Func) { - assert(payload->size() == 1 && "CreateObject: A bytecode function requires a body node"); - obj = objects::make_func(new Bytecode { payload->at(0) }); - } else { - assert(false && "CreateObject has to specify a value"); + rt::objects::DynObject* parent_obj = nullptr; + if (!frame_stack.empty()) + { + parent_obj = frame_stack.back()->frame; } - stack.push_back(obj); - std::cout << "push " << obj << std::endl; - return ExecNext {}; + auto frame = new InterpreterFrame{ + body->begin(), body, rt::make_frame(parent_obj), {}}; + frame_stack.push_back(frame); + return frame; } - - if (node == Null) + InterpreterFrame* pop_stack_frame() { - stack.push_back(nullptr); - std::cout << "push nullptr" << std::endl; - return ExecNext {}; - } + auto frame = frame_stack.back(); + frame_stack.pop_back(); + delete frame; - if (node == LoadFrame) - { - auto frame = objects::get_frame(); - std::string field{node->location().view()}; - auto v = objects::get(frame, field); - objects::add_reference(frame, v); - stack.push_back(v); - std::cout << "push " << v << std::endl; - return ExecNext {}; + if (frame_stack.empty()) + { + return nullptr; + } + else + { + return frame_stack.back(); + } } - - if (node == StoreFrame) + InterpreterFrame* parent_stack_frame() { - auto frame = objects::get_frame(); - auto v = pop("value to store"); - std::string field{node->location().view()}; - auto v2 = objects::set(frame, field, v); - remove_reference(frame, v2); - return ExecNext {}; + return frame_stack[frame_stack.size() - 2]; } - if (node == LoadField) + std::vector& stack() { - assert(stack.size() >= 2 && "the stack is too small"); - auto k = pop("lookup-key"); - auto v = pop("lookup-value"); - - if (!v) { - std::cerr << std::endl; - std::cerr << "Error: Tried to access a field on `None`" << std::endl; - std::abort(); - } - - auto v2 = objects::get(v, k); - stack.push_back(v2); - std::cout << "push " << v2 << std::endl; - objects::add_reference(objects::get_frame(), v2); - objects::remove_reference(objects::get_frame(), k); - objects::remove_reference(objects::get_frame(), v); - return ExecNext {}; + return frame_stack.back()->stack; } - - if (node == StoreField) + rt::objects::DynObject* frame() { - auto v = pop("value to store"); - auto k = pop("lookup-key"); - auto v2 = pop("lookup-value"); - auto v3 = objects::set(v2, k, v); - move_reference(objects::get_frame(), v2, v); - remove_reference(objects::get_frame(), k); - remove_reference(objects::get_frame(), v2); - remove_reference(v2, v3); - return ExecNext {}; + return frame_stack.back()->frame; } - if (node == CreateRegion) + rt::objects::DynObject* pop(char const* data_info) { - auto v = pop("region source"); - objects::create_region(v); - remove_reference(objects::get_frame(), v); - return ExecNext {}; + auto v = stack().back(); + stack().pop_back(); + std::cout << "pop " << v << " (" << data_info << ")" << std::endl; + return v; } - if (node == FreezeObject) + std::variant + run_stmt(trieste::Node& node) { - auto v = pop("object to freeze"); - objects::freeze(v); - remove_reference(objects::get_frame(), v); - return ExecNext {}; - } + // ========================================== + // Operators that shouldn't be printed + // ========================================== + if (node == Print) + { + // Console output + std::cout << node->location().view() << std::endl << std::endl; + + // Mermaid output + std::vector edges{{nullptr, "?", frame()}}; + ui->output(edges, std::string(node->location().view())); + + // Continue + return ExecNext{}; + } + if (node == Label) + { + return ExecNext{}; + } - if (node == Eq || node == Neq) - { - auto b = pop("Rhs"); - auto a = pop("Lhs"); + // ========================================== + // Operators that should be printed + // ========================================== + std::cout << "Op: " << node->type().str() << std::endl; + if (node == CreateObject) + { + rt::objects::DynObject* obj = nullptr; + + assert( + !node->empty() && "CreateObject has to specify the type of data"); + auto payload = node->at(0); + if (payload == Dictionary) + { + obj = rt::make_object(); + } + else if (payload == String) + { + obj = rt::make_str(std::string(payload->location().view())); + } + else if (payload == KeyIter) + { + auto v = pop("iterator source"); + obj = rt::make_iter(v); + rt::remove_reference(frame(), v); + } + else if (payload == Proto) + { + obj = rt::make_object(); + // RC transferred + rt::set_prototype(obj, pop("prototype source")); + } + else if (payload == Func) + { + assert( + payload->size() == 1 && + "CreateObject: A bytecode function requires a body node"); + obj = rt::make_func(new Bytecode{payload->at(0)}); + } + else + { + assert(false && "CreateObject has to specify a value"); + } - auto bool_result = (a == b); - if (node == Neq) { - bool_result = !bool_result; + stack().push_back(obj); + std::cout << "push " << obj << std::endl; + return ExecNext{}; } - std::string result; - if (bool_result) { - result = "True"; - } else { - result = "False"; + if (node == Null) + { + stack().push_back(nullptr); + std::cout << "push nullptr" << std::endl; + return ExecNext{}; } - auto frame = objects::get_frame(); - auto v = objects::get(frame, result); - objects::add_reference(frame, v); - stack.push_back(v); - std::cout << "push " << v << " (" << result << ")"<< std::endl; - - remove_reference(objects::get_frame(), a); - remove_reference(objects::get_frame(), b); - return ExecNext {}; - } - if (node == Jump) - { - return ExecJump { node->location() }; - } + if (node == LoadFrame) + { + std::string field{node->location().view()}; + auto v = rt::get(frame(), field); + rt::add_reference(frame(), v); + stack().push_back(v); + std::cout << "push " << v << std::endl; + return ExecNext{}; + } - if (node == JumpFalse) - { - auto v = pop("jump condition"); - auto false_obj = objects::get(objects::get_frame(), "False"); - auto jump = (v == false_obj); - remove_reference(objects::get_frame(), v); - if (jump) { - return ExecJump { node->location() }; - } else { - return ExecNext {}; + if (node == StoreFrame) + { + auto v = pop("value to store"); + std::string field{node->location().view()}; + auto v2 = rt::set(frame(), field, v); + rt::remove_reference(frame(), v2); + return ExecNext{}; } - } - if (node == IterNext) - { - auto it = pop("iterator"); + if (node == LoadField) + { + assert(stack().size() >= 2 && "the stack is too small"); + auto k = pop("lookup-key"); + auto v = pop("lookup-value"); + + if (!v) + { + std::cerr << std::endl; + std::cerr << "Error: Tried to access a field on `None`" << std::endl; + std::abort(); + } - auto obj = objects::value::iter_next(it); - remove_reference(objects::get_frame(), it); + auto v2 = rt::get(v, k); + stack().push_back(v2); + std::cout << "push " << v2 << std::endl; + rt::add_reference(frame(), v2); + rt::remove_reference(frame(), k); + rt::remove_reference(frame(), v); + return ExecNext{}; + } - stack.push_back(obj); - std::cout << "push " << obj << " (next from iter)" << std::endl; - return ExecNext {}; - } + if (node == StoreField) + { + auto v = pop("value to store"); + auto k = pop("lookup-key"); + auto v2 = pop("lookup-value"); + auto v3 = rt::set(v2, k, v); + rt::move_reference(frame(), v2, v); + rt::remove_reference(frame(), k); + rt::remove_reference(frame(), v2); + rt::remove_reference(v2, v3); + return ExecNext{}; + } - if (node == ClearStack) { - if (!stack.empty()) { - while (!stack.empty()) { - remove_reference(objects::get_frame(), stack.back()); - stack.pop_back(); - } + if (node == CreateRegion) + { + auto v = pop("region source"); + rt::create_region(v); + rt::remove_reference(frame(), v); + return ExecNext{}; } - return ExecNext {}; - } - if (node == Call) { - auto func = pop("function"); - auto arg_ctn = std::stoul(std::string(node->location().view())); - auto action = ExecFunc { objects::value::get_bytecode(func)->body , arg_ctn }; - remove_reference(objects::get_frame(), func); - return action; - } + if (node == FreezeObject) + { + auto v = pop("object to freeze"); + rt::freeze(v); + rt::remove_reference(frame(), v); + return ExecNext{}; + } - if (node == Return) { - return ExecReturn {}; - } + if (node == Eq || node == Neq) + { + auto b = pop("Rhs"); + auto a = pop("Lhs"); - if (node == ReturnValue) { - auto value = pop("return value"); - // RC is transfered to the stack of the parent frame - return ExecReturn { value }; - } + auto bool_result = (a == b); + if (node == Neq) + { + bool_result = !bool_result; + } - std::cerr << "unhandled bytecode" << std::endl; - node->str(std::cerr); - std::abort(); - } + const char* result_str; + rt::objects::DynObject* result; + if (bool_result) + { + result = rt::get_true(); + result_str = "true"; + } + else + { + result = rt::get_false(); + result_str = "false"; + } + stack().push_back(result); + std::cout << "push " << result << " (" << result_str << ")" + << std::endl; + + rt::remove_reference(frame(), a); + rt::remove_reference(frame(), b); + return ExecNext{}; + } -public: - Interpreter(objects::UI* ui_): ui(ui_) {} - - void run(trieste::Node main) { - // Push the main frame, to later clear the locally defined functions and vars - objects::push_frame(); - - auto it = main->begin(); - auto body = main; - - while (it != body->end()) { - const auto action = run_stmt(*it); - - if (std::holds_alternative(action)) { - it++; - } else if (std::holds_alternative(action)) { - auto jump = std::get(action); - auto label_node = body->look(jump.target); - assert(label_node.size() == 1); - it = body->find(label_node[0]); - // Skip the label node - it++; - } else if (std::holds_alternative(action)) { - auto func = std::get(action); - - // Make sure the stored it, continues after the call - it++; - - // Store the current frame - auto parent_frame = new InterpreterFrame { std::move(stack), it, body, objects::get_frame() }; - frame_stack.push_back(parent_frame); - objects::push_frame(); - - // Setup the new frame - body = func.body; - it = body->begin(); - stack = {}; - for (size_t i = 0; i < func.arg_ctn; i++) { - stack.push_back(parent_frame->stack.back()); - parent_frame->stack.pop_back(); + if (node == Jump) + { + return ExecJump{node->location()}; + } + + if (node == JumpFalse) + { + auto v = pop("jump condition"); + auto jump = (v == rt::get_false()); + if (jump) + { + return ExecJump{node->location()}; + } + else + { + return ExecNext{}; } - } else if (std::holds_alternative(action)) { - it = body->end(); - } else { - assert(false && "unhandled statement action"); } - if (it == body->end() && !frame_stack.empty()) { - // Handle frame stack poping - auto frame = frame_stack.back(); - frame_stack.pop_back(); - objects::pop_frame(); + if (node == IterNext) + { + auto it = pop("iterator"); - assert(frame->frame == objects::get_frame() && "the interpreter and object frames need to be synced"); + auto obj = rt::iter_next(it); + rt::remove_reference(frame(), it); - it = frame->ip; - body = frame->body; - stack = frame->stack; - delete frame; + stack().push_back(obj); + std::cout << "push " << obj << " (next from iter)" << std::endl; + return ExecNext{}; + } - if (std::holds_alternative(action)) { - auto return_ = std::get(action); - if (return_.value.has_value()) { - stack.push_back(return_.value.value()); + if (node == ClearStack) + { + if (!stack().empty()) + { + while (!stack().empty()) + { + rt::remove_reference(frame(), stack().back()); + stack().pop_back(); } } + return ExecNext{}; + } + + if (node == Call) + { + auto func = pop("function"); + auto arg_ctn = std::stoul(std::string(node->location().view())); + auto action = ExecFunc{rt::get_bytecode(func)->body, arg_ctn}; + rt::remove_reference(frame(), func); + return action; } + + if (node == Return) + { + return ExecReturn{}; + } + + if (node == ReturnValue) + { + auto value = pop("return value"); + // RC is transfered to the stack of the parent frame + return ExecReturn{value}; + } + + std::cerr << "unhandled bytecode" << std::endl; + node->str(std::cerr); + std::abort(); } - // Pop the main frame, to clear local functions and variables - objects::pop_frame(); - } -}; + public: + Interpreter(rt::ui::UI* ui_) : ui(ui_) {} -class UI : public objects::UI -{ - bool interactive; - std::string path; - std::ofstream out; - -public: - UI(bool interactive_) : interactive(interactive_) { - path = "mermaid.md"; - out.open(path); - } + void run(trieste::Node main) + { + auto frame = push_stack_frame(main); + + while (frame) + { + const auto action = run_stmt(*frame->ip); + + if (std::holds_alternative(action)) + { + frame->ip++; + } + else if (std::holds_alternative(action)) + { + auto jump = std::get(action); + auto label_node = frame->body->look(jump.target); + assert(label_node.size() == 1); + frame->ip = frame->body->find(label_node[0]); + // Skip the label node + frame->ip++; + } + else if (std::holds_alternative(action)) + { + auto func = std::get(action); + + // Make sure the stored ip, continues after the call + frame->ip++; + + frame = push_stack_frame(func.body); + auto parent_frame = parent_stack_frame(); + + // Setup the new frame + for (size_t i = 0; i < func.arg_ctn; i++) + { + frame->stack.push_back(parent_frame->stack.back()); + parent_frame->stack.pop_back(); + } + } + else if (std::holds_alternative(action)) + { + frame->ip = frame->body->end(); + } + else + { + assert(false && "unhandled statement action"); + } - void output(std::vector &edges, std::string message) { - out << "```" << std::endl; - out << message << std::endl; - out << "```" << std::endl; - objects::mermaid(edges, out); - if (interactive) { - out.close(); - std::cout << "Press a key!" << std::endl; - getchar(); + if (frame->ip == frame->body->end()) + { + if (std::holds_alternative(action)) + { + auto return_ = std::get(action); + if (return_.value.has_value()) + { + auto parent = parent_stack_frame(); + auto value = return_.value.value(); + rt::move_reference(frame->frame, parent->frame, value); + parent->stack.push_back(value); + } + } + + frame = pop_stack_frame(); + } + } + } + }; + + class UI : public rt::ui::UI + { + bool interactive; + std::string path; + std::ofstream out; + + public: + UI(bool interactive_) : interactive(interactive_) + { + path = "mermaid.md"; out.open(path); } - } -}; -void start(trieste::Node main_body, bool interactive) { - size_t initial = objects::pre_run(); + void output(std::vector& edges, std::string message) + { + out << "```" << std::endl; + out << message << std::endl; + out << "```" << std::endl; + rt::ui::mermaid(edges, out); + if (interactive) + { + out.close(); + std::cout << "Press a key!" << std::endl; + getchar(); + out.open(path); + } + } + }; - UI ui(interactive); - Interpreter inter(&ui); - inter.run(main_body); + void start(trieste::Node main_body, bool interactive) + { + size_t initial = rt::pre_run(); - objects::post_run(initial, ui); -} + UI ui(interactive); + Interpreter inter(&ui); + inter.run(main_body); + + rt::post_run(initial, ui); + } -} // namespace verona::interpreter \ No newline at end of file +} // namespace verona::interpreter diff --git a/src/lang/interpreter.h b/src/lang/interpreter.h index a714c7d..4227bd9 100644 --- a/src/lang/interpreter.h +++ b/src/lang/interpreter.h @@ -1,25 +1,8 @@ #pragma once -#include -#include +namespace verona::interpreter +{ + struct Bytecode; -namespace objects { - struct UI; - class DynObject; + void delete_bytecode(Bytecode* bytecode); } - -namespace verona::interpreter { - struct Bytecode; - - void delete_bytecode(Bytecode* bytecode); -} - -namespace verona::interpreter { -/// @brief This executes the the given function body with the given stack. -/// The frame should be pushed and poped by the function callee. It's recommendable -/// to have a print at the start and end of the function body. -/// -/// The stack holds the arguments in reverse order of their definition. They have to -/// be popped of in order. -std::optional run_body(Bytecode *body, std::vector &stack, objects::UI* ui); -} // namespace verona::interpreter diff --git a/src/lang/lang.cc b/src/lang/lang.cc index b092a4a..4155039 100644 --- a/src/lang/lang.cc +++ b/src/lang/lang.cc @@ -1,747 +1,16 @@ -#include "bytecode.h" -#include "trieste/driver.h" -#include "trieste/trieste.h" +#include "lang.h" + #include "interpreter.h" -#include +#include "trieste/driver.h" -#define TAB_SIZE 4 +#include using namespace trieste; -inline const TokenDef Ident{"ident", trieste::flag::print}; -inline const TokenDef Assign{"assign"}; -inline const TokenDef Create{"create"}; -inline const TokenDef For{"for"}; -inline const TokenDef If{"if"}; -inline const TokenDef Else{"else"}; -inline const TokenDef Block{"block"}; -inline const TokenDef Empty{"empty"}; -inline const TokenDef Drop{"drop"}; -inline const TokenDef Freeze{"freeze"}; -inline const TokenDef Region{"region"}; -inline const TokenDef Lookup{"lookup"}; -inline const TokenDef Parens{"parens"}; - -inline const TokenDef Op{"op"}; -inline const TokenDef Rhs{"rhs"}; -inline const TokenDef Lhs{"lhs"}; -inline const TokenDef Key{"key"}; -inline const TokenDef Value{"value"}; - -namespace verona::wf { -using namespace trieste::wf; - -inline const auto parse_tokens = Region | Ident | Lookup | Empty | Freeze | Drop | Null | String | Create | Parens; -inline const auto parse_groups = Group | Assign | If | Else | Block | For | Func | List | Return; - -inline const auto parser = - (Top <<= File) - | (File <<= parse_groups++) - | (Assign <<= Group++) - | (If <<= Group * Eq * Group) - | (Else <<= Group * Group) - | (Group <<= (parse_tokens | Block | List)++) - | (Block <<= (parse_tokens | parse_groups)++) - | (Eq <<= Group * Group) - | (Lookup <<= Group) - | (For <<= Group * List * Group * Group) - | (List <<= Group++) - | (Parens <<= (Group | List)++) - | (Func <<= Group * Group * Group) - | (Return <<= Group++) - ; - -inline const auto lv = Ident | Lookup; -inline const auto rv = lv | Empty | Null | String | Create | Call; -inline const auto cmp_values = Ident | Lookup | Null; -inline const auto key = Ident | Lookup | String; - -inline const auto grouping = - (Top <<= File) - | (File <<= Body) - | (Body <<= Block) - | (Block <<= (Freeze | Region | Assign | If | For | Func | Return | ReturnValue | Call)++) - | (Assign <<= (Lhs >>= lv) * (Rhs >>= rv)) - | (Lookup <<= (Lhs >>= lv) * (Rhs >>= key)) - | (Region <<= Ident) - | (Freeze <<= Ident) - | (Create <<= Ident) - | (If <<= Eq * Block * Block) - | (For <<= (Key >>= Ident) * (Value >>= Ident) * (Op >>= lv) * Block) - | (Eq <<= (Lhs >>= cmp_values) * (Rhs >>= cmp_values)) - | (Func <<= Ident * Params * Body) - | (Call <<= (Op >>= key) * List) - | (ReturnValue <<= rv) - | (List <<= rv++) - | (Params <<= Ident++) - ; -inline const auto stmt_prints = - grouping - | (Block <<= (Freeze | Region | Assign | If | For | Func | Return | ReturnValue | Call | ClearStack | Print)++); -} // namespace verona::wf - -struct Indent { - size_t indent; - Token toc; -}; - -trieste::Parse parser() { - Parse p{depth::file, verona::wf::parser}; - - auto indent = std::make_shared>(); - indent->push_back({0, File}); - - auto update_indent = [indent](detail::Make& m, size_t this_indent) { - m.term({Assign, Return}); - - if (this_indent > indent->back().indent) { - std::cout << "this_indent " << this_indent << std::endl; - std::cout << "indent->back().indent " << indent->back().indent << std::endl; - m.error("unexpected indention"); - } - - while (this_indent < indent->back().indent) { - m.term({Block, Group, indent->back().toc}); - indent->pop_back(); - } - - if (this_indent != indent->back().indent) { - m.error("unexpected indention"); - } - }; - - p("start", - { - // Empty lines should be ignored - "[ \\t]*\\r?\\n" >> [](auto &) {}, - - // Line comment - "(?:#[^\\n]*)" >> [](auto &) {}, - - // Indention - " +" >> [update_indent](auto &m) { - if (m.match().len % TAB_SIZE != 0) { - m.error("unexpected indention"); - } - auto this_indent = m.match().len / TAB_SIZE; - update_indent(m, this_indent); - m.mode("contents"); - }, - - "\\t*" >> [update_indent](auto &m) { - auto this_indent = m.match().len; - update_indent(m, this_indent); - m.mode("contents"); - }, - }); - - p( - "contents", - { - // Indentation - "\\r?\\n" >> [](auto &m) { - m.mode("start"); - }, - - "[[:blank:]]+" >> [](auto &) {}, - - // Line comment - "(?:#[^\\n\\r]*)" >> [](auto &) {}, - - "def" >> [](auto &m) { - m.seq(Func); - }, - "\\(" >> [](auto &m) { - m.push(Parens); - }, - "\\)" >> [](auto &m) { - m.term({List, Parens}); - m.extend(Parens); - }, - "return" >> [](auto &m) { - m.seq(Return); - }, - - "for" >> [](auto &m) { - m.seq(For); - }, - "in" >> [](auto &m) { - // In should always be in a list from the identifiers. - m.term({List}); - }, - "," >> [](auto &m) { - m.seq(List); - }, - - "if" >> [](auto &m) { - m.seq(If); - }, - "else" >> [](auto &m) { - m.seq(Else); - }, - ":" >> [indent](auto &m) { - // Exit conditionals expressions. - m.term({Eq}); - - Token toc = Empty; - if (m.in(If)) { - toc = If; - } else if (m.in(Else)) { - toc = Else; - } else if (m.in(For)) { - toc = For; - } else if (m.in(Func)) { - toc = Func; - } else { - m.error("unexpected colon"); - return; - } - assert(toc != Empty); - - auto current_indent = indent->back().indent; - auto next_indent = current_indent + 1; - indent->push_back({next_indent, toc}); - - if (m.in(Group)) { - m.pop(Group); - } - - m.push(Block); - }, - "drop" >> [](auto &m) { m.add(Drop); }, - "create" >> [](auto &m) { m.add(Create); }, - "freeze" >> [](auto &m) { m.add(Freeze); }, - "region" >> [](auto &m) { m.add(Region); }, - "None" >> [](auto &m) { m.add(Null); }, - "[0-9A-Za-z_]+" >> [](auto &m) { m.add(Ident); }, - "\\[" >> [](auto &m) { - m.push(Lookup); - }, - "\\]" >> [](auto &m) { - m.term({Lookup}); - }, - "\\.([0-9A-Za-z_]+)" >> [](auto &m) { - m.push(Lookup); - m.add(String, 1); - m.term({Lookup}); - }, - "\"([^\\n\"]+)\"" >> [](auto &m) { m.add(String, 1); }, - "==" >> [](auto &m) { m.seq(Eq); }, - "=" >> [](auto &m) { m.seq(Assign); }, - "{}" >> [](auto &m) { m.add(Empty); }, - }); - - p.done([update_indent](auto &m) { - update_indent(m, 0); - }); - - return p; -} - -auto LV = T(Ident, Lookup); -auto RV = T(Empty, Ident, Lookup, Null, String, Create, Call); -auto CMP_V = T(Ident, Lookup, Null); -auto KEY = T(Ident, Lookup, String); - -Node create_print(size_t line, std::string text) { - std::stringstream ss; - ss << "Line " << line << ": " << text; - return NodeDef::create(Print, ss.str()); -} -Node create_print(Node from, std::string text) { - auto [line, col] = from->location().linecol(); - return create_print(line + 1, text); -} -Node create_print(Node from) { - return create_print(from, std::string(from->location().view())); -} -Node create_from(Token def, Node from) { - return NodeDef::create(def, from->location()); -} -std::string expr_header(Node expr) { - auto expr_str = expr->location().view(); - return std::string(expr_str.substr(0, expr_str.find(":") + 1)); -} - -PassDef grouping() { - PassDef p{ - "grouping", - verona::wf::grouping, - dir::bottomup, - { - // Normalize so that all expressions are inside bodies and blocks. - T(File) << (--T(Body) * Any++[File]) >> - [](auto& _) { return File << (Body << (Block << _[File])); }, - - In(Group) * LV[Lhs] * (T(Lookup)[Lookup] << (T(Group) << KEY[Rhs])) >> - [](auto &_) { return Lookup << _[Lhs] << _(Rhs); }, - - T(Group) << ((T(Region)[Region] << End) * T(Ident)[Ident] * End) >> - [](auto &_) { - _(Region)->extend(_(Ident)->location()); - return _(Region) << _(Ident); - }, - - T(Group) << ((T(Freeze)[Freeze] << End) * T(Ident)[Ident] * End) >> - [](auto &_) { - _(Freeze)->extend(_(Ident)->location()); - return _(Freeze) << _(Ident); - }, - - T(Group) << ((T(Drop)[Drop] << End) * LV[Lhs] * End) >> - [](auto &_) { - return Assign << _(Lhs) << Null; - }, - // function(arg, arg) - --In(Func) * ( - T(Group)[Group] << ( - (T(Ident)[Ident]) * - (T(Parens)[Parens] << (~T(List)[List])) * - End)) >> - [](auto &_) { - auto list = _(List); - if (!list) { - list = create_from(List, _(Parens)); - } - - return create_from(Call, _(Group)) - << _(Ident) - << list; - }, - - T(Group) << ((T(Create)[Create] << End) * T(Ident)[Ident] * End) >> - [](auto &_) { - _(Create)->extend(_(Ident)->location()); - return Group << (_(Create) << _(Ident)); - }, - - T(Assign) << ((T(Group) << LV[Lhs] * End) * - ((T(Group) << (RV[Rhs] * End)) / (RV[Rhs] * End)) * End) >> - [](auto &_) { return Assign << _[Lhs] << _[Rhs]; }, - T(Eq) << ((T(Group) << CMP_V[Lhs] * End) * - (T(Group) << CMP_V[Rhs] * End) * End) >> - [](auto &_) { return Eq << _[Lhs] << _[Rhs]; }, - - (T(If) << (T(Group) * T(Eq)[Eq] * (T(Group) << T(Block)[Block]))) >> - [](auto &_) { - return If << _(Eq) << _(Block); - }, - (T(Else) << (T(Group) * (T(Group) << T(Block)[Block]))) >> - [](auto &_) { - return Else << _(Block); - }, - (T(If)[If] << (T(Eq) * T(Block) * End)) * (T(Else) << T(Block)[Block]) >> - [](auto &_) { - return _(If) << _(Block); - }, - (T(If)[If] << (T(Eq) * T(Block) * End)) * (--T(Else)) >> - [](auto &_) { - // This adds an empty else block, if no else was written - return _(If) << Block; - }, - - In(List) * (T(Group) << (RV[Rhs] * End)) >> [](auto &_) { - return _(Rhs); - }, - - T(For)[For] << ( - (T(Group)) * - (T(List) << (T(Ident)[Key] * T(Ident)[Value] * End)) * - (T(Group) << (LV[Op] * End)) * - (T(Group) << (T(Block)[Block] * End)) * - End) >> - [](auto &_) { - // This function only has a block for now, as a lowering pass still needs to - // do some modifications until it's a "full" body. - return create_from(For, _(For)) - << _(Key) - << _(Value) - << _(Op) - << _(Block); - }, - - T(Func)[Func] << ( - (T(Group) << End) * - (T(Group) << ( - (T(Ident)[Ident]) * - (T(Parens)[Parens] << (~(T(List) << T(Ident)++[List]))) * - End)) * - (T(Group) << T(Block)[Block]) * - End) >> - [](auto &_) { - return create_from(Func, _(Func)) - << _(Ident) - << (create_from(Params, _(Parens)) << _[List]) - << (Body << _(Block)); - }, - - T(Return)[Return] << ( - (T(Group) << End) * - End) >> - [](auto &_) { - return create_from(Return, _(Return)); - }, - T(Return)[Return] << ( - (T(Group) << End) * - (T(Group) << (RV[Rhs] * End)) * - End) >> - [](auto &_) { - return create_from(ReturnValue, _(Return)) << _(Rhs); - }, - - }}; - - - return p; -} - -PassDef stmt_prints() { - PassDef p{ - "stmt_prints", - verona::wf::stmt_prints, - dir::bottomup | dir::once, - { - In(Block) * T(Call)[Call] >> - [](auto &_) { - return Seq - << _(Call) - << ClearStack - << create_print(_(Call)); - }, - } - }; - - return p; -} - -Node return_ident() { - return Ident ^ "return"; -} - -inline const TokenDef Compile{"compile"}; - -namespace verona::wf { -using namespace trieste::wf; -inline const trieste::wf::Wellformed flatten = - (Top <<= File) - | (File <<= Body) - | (Body <<= (Freeze | Region | Assign | Eq | Neq | Label | Jump | JumpFalse | - Print | StoreFrame | LoadFrame | CreateObject | Ident | IterNext | - Create | StoreField | Lookup | String | Call | Return | ReturnValue | - ClearStack)++) - | (CreateObject <<= (KeyIter | String | Dictionary | Func)) - | (Func <<= Compile) - | (Compile <<= Body) - | (Create <<= Ident) - | (Assign <<= (Lhs >>= lv) * (Rhs >>= rv)) - | (Lookup <<= (Lhs >>= lv) * (Rhs >>= key)) - | (Region <<= Ident) - | (Freeze <<= Ident) - | (Call <<= (Op >>= key) * List) - | (List <<= rv++) - | (Params <<= Ident++) - | (Eq <<= (Lhs >>= cmp_values) * (Rhs >>= cmp_values)) - | (Neq <<= (Lhs >>= cmp_values) * (Rhs >>= cmp_values)) - | (Label <<= Ident)[Ident]; - ; -} // namespace verona::wf - -int g_jump_label_counter = 0; -std::string new_jump_label() +std::pair>> extract_bytecode_pass() { - g_jump_label_counter += 1; - return "label_"+std::to_string(g_jump_label_counter); -} - -int g_iter_name = 0; -std::string new_iter_name() -{ - g_iter_name += 1; - return "_iter_"+std::to_string(g_iter_name); -} - -PassDef flatten() { - return { - "flatten", - verona::wf::flatten, - dir::bottomup, - { - In(Body) * (T(Block) << Any++[Block]) >> - [](auto &_) { - return Seq << _[Block]; - }, - T(If)[If] << (T(Eq)[Op] * (T(Block) << Any++[Lhs]) * (T(Block) << Any++[Rhs])) >> - [](auto &_) { - auto else_label = new_jump_label(); - auto join_label = new_jump_label(); - - auto if_head = expr_header(_(If)); - - return Seq << _(Op) - << (JumpFalse ^ else_label) - // Then block - << create_print(_(If), if_head + " (True)") - << _[Lhs] - << (Jump ^ join_label) - // Else block - << ((Label ^ "else:") << (Ident ^ else_label)) - << create_print(_(If), if_head + " (False)") - << _[Rhs] - // Join - << (Label << (Ident ^ join_label)) - ; - }, - T(For)[For] << ( - T(Ident)[Key] * - T(Ident)[Value] * - LV[Op] * - (T(Block) << Any++[Block]) * - End) >> - [](auto &_) { - auto it_name = new_iter_name(); - - auto start_label = new_jump_label(); - auto break_label = new_jump_label(); - - auto for_head = expr_header(_(For)); - - return Seq - // Prelude - << clone(_(Op)) - << (CreateObject << KeyIter) - << (StoreFrame ^ it_name) - << (Ident ^ it_name) - << (String ^ "source") - << _(Op) - << (StoreField) - << create_print(_(For), "create " + it_name) - << ((Label ^ "start:") << (Ident ^ start_label)) - // key = iter++ - << (Ident ^ it_name) - << (IterNext) - << create_from(StoreFrame, _(Key)) - // While (key != null) - << (Neq - << create_from(Ident, _(Key)) - << Null) - << (JumpFalse ^ break_label) - // value = it.source.key - << (Lookup - << (Lookup - << (Ident ^ it_name) - << (String ^ "source")) - << create_from(Ident, _(Key))) - << create_from(StoreFrame, _(Value)) - << create_print(_(For), for_head + " (Next)") - // Block - << _[Block] - // Final cleanup - << (Jump ^ start_label) - << ((Label ^ "break:") << (Ident ^ break_label)) - << create_print(_(For), for_head + " (Break)") - << ((Assign ^ ("drop " + std::string(_(Value)->location().view()))) << create_from(Ident, _(Value)) << Null) - << ((Assign ^ ("drop " + it_name)) << (Ident ^ it_name) << Null); - }, - - T(Func)[Func] << ( - T(Ident)[Ident] * - T(Params)[Params] * - (T(Body)[Body] << Any++[Block])* - End) >> - [](auto &_) { - auto func_head = expr_header(_(Func)); - - // Function setup - Node body = Body; - Node args = _(Params); - for (auto it = args->begin(); it != args->end(); it++) { - body << create_from(StoreFrame, *it); - } - body << create_print(_(Func), func_head + " (Enter)"); - - // Function body - auto block = _[Block]; - for (auto stmt : block) { - if (stmt == ReturnValue) { - body << (create_from(Assign, stmt) << return_ident() << stmt->at(0)); - body << (LoadFrame ^ "return"); - body << create_from(ReturnValue, stmt); - } else if (stmt == Return) { - body << create_print(stmt); - body << stmt; - } else { - body << stmt; - } - } - body << create_print(_(Func), func_head + " (Exit)"); - - // Function cleanup - return Seq - << (CreateObject << (Func << (Compile << body))) - << (StoreFrame ^ _(Ident)) - << create_print(_(Func), func_head); - }, - } - }; -} - -inline const TokenDef Prelude{"prelude"}; -inline const TokenDef Postlude{"postlude"}; - -namespace verona::wf { -using namespace trieste::wf; -inline const trieste::wf::Wellformed bytecode = - empty | (Body <<= (LoadFrame | StoreFrame | LoadField | StoreField | Drop | Null | - CreateObject | CreateRegion | FreezeObject | IterNext | Print | - Eq | Neq | Jump | JumpFalse | Label | Call | Return | ReturnValue | - ClearStack)++) - | (CreateObject <<= (Dictionary | String | KeyIter | Proto | Func)) - | (Top <<= Body) - | (Func <<= Body) - | (Label <<= Ident)[Ident]; -} // namespace verona::wf - -PassDef bytecode() { - PassDef p{"bytecode", - verona::wf::bytecode, - dir::topdown, - { - T(File) << T(Body)[Body] >> - [](auto &_) { - return Body - << Prelude - << (Compile << *_[Body]) - << Postlude; - }, - T(Compile) << (T(Body)[Body] << Any++[Block]) >> - [](auto &_) { - return create_from(Body, _(Body)) << (Compile << _[Block]); - }, - - T(Prelude) >> - [](auto &) { - return Seq - << (CreateObject << (String ^ "True")) - << (StoreFrame ^ "True") - << (LoadFrame ^ "True") - << FreezeObject - << (CreateObject << (String ^ "False")) - << (StoreFrame ^ "False") - << (LoadFrame ^ "False") - << FreezeObject - << create_print(0, "prelude"); - }, - T(Postlude) >> - [](auto &) { - return Seq - << Null - << (StoreFrame ^ "True") - << Null - << (StoreFrame ^ "False") - << create_print(0, "postlude"); - }, - - T(Compile) << (Any[Lhs] * (Any * Any++)[Rhs]) >> - [](auto &_) { - return Seq << (Compile << _[Lhs]) - << (Compile << _[Rhs]); - }, - - // The node doesn't require additional processing and should be copied - T(Compile) << T( - Null, Label, Print, Jump, JumpFalse, CreateObject, - StoreFrame, LoadFrame, IterNext, StoreField, Return, - ReturnValue, ClearStack)[Op] >> - [](auto &_) -> Node { return _(Op); }, - - T(Compile) << (T(Eq, Neq)[Op] << (Any[Lhs] * Any[Rhs])) >> - [](auto &_) { - return Seq << (Compile << _(Lhs)) - << (Compile << _(Rhs)) - << _(Op)->type(); - }, - - T(Compile) << (T(Ident)[Ident]) >> - [](auto &_) { return create_from(LoadFrame, _(Ident)); }, - - T(Compile) << (T(Lookup)[Lookup] << (Any[Op] * Any[Key] * End)) >> - [](auto &_) { - return Seq << (Compile << _[Op]) - << (Compile << _[Key]) - << create_from(LoadField, _(Lookup)); - }, - - T(Compile) << (T(Assign)[Op] << (T(Ident)[Ident] * Any[Rhs])) >> - [](auto &_) { - return Seq << (Compile << _[Rhs]) - << create_from(StoreFrame, _(Ident)) - << create_print(_(Op)); - }, - - T(Compile) << (T(Assign)[Assign] << (( - T(Lookup)[Lookup] << (Any[Op] * Any[Key] * End)) * - Any[Rhs])) >> - [](auto &_) { - return Seq << (Compile << _[Op]) - << (Compile << _[Key]) - << (Compile << _[Rhs]) - << create_from(StoreField, _(Lookup)) - << create_print(_(Assign)); - }, - - T(Compile) << (T(Freeze)[Op] << T(Ident)[Ident]) >> - [](auto &_) { - return Seq << (Compile << _[Ident]) - << FreezeObject - << create_print(_(Op)); - }, - - T(Compile) << (T(Create)[Op] << T(Ident)[Ident]) >> - [](auto &_) { - return Seq << (Compile << _[Ident]) - << (CreateObject << Proto); - }, - - T(Compile) << (T(Region)[Op] << T(Ident)[Ident]) >> - [](auto &_) { - return Seq << (Compile << _[Ident]) - << CreateRegion - << create_print(_(Op)); - }, - - T(Compile) << ( - T(Call)[Call] << ( - KEY[Op] * - (T(List) << Any++[List]) * - End)) >> - [](auto &_) { - // The print is done by the called function - return Seq - << (Compile << _[List]) - << (Compile << _(Op)) - << (Call ^ std::to_string(_[List].size())); - }, - - T(Compile) << End >> - [](auto &) -> Node { return {}; }, - - T(Compile) << (T(Empty)) >> - [](auto &) -> Node { return CreateObject << Dictionary; }, - T(Compile) << (T(String)[String]) >> - [](auto &_) -> Node { return CreateObject << _(String); }, - - }}; - return p; -} - -std::pair>> extract_bytecode_pass() { auto result = std::make_shared>(); - PassDef p{ - "interpreter", - verona::wf::bytecode, - dir::topdown | dir::once, - {} - }; + PassDef p{"interpreter", verona::wf::bytecode, dir::topdown | dir::once, {}}; p.post(trieste::Top, [result](Node n) { *result = n->at(0); return 0; @@ -750,7 +19,8 @@ std::pair>> extract_bytecode_pass() return {p, result}; } -namespace verona::interpreter { +namespace verona::interpreter +{ void start(trieste::Node main_body, bool interactive); } @@ -758,20 +28,26 @@ struct CLIOptions : trieste::Options { bool iterative = false; - void configure(CLI::App &app) + void configure(CLI::App& app) { - app.add_flag("-i,--interactive", iterative, "Run the interpreter iteratively"); + app.add_flag( + "-i,--interactive", iterative, "Run the interpreter iteratively"); } }; -int load_trieste(int argc, char **argv) { +int load_trieste(int argc, char** argv) +{ CLIOptions options; auto [extract_bytecode, result] = extract_bytecode_pass(); - trieste::Reader reader{"verona_dyn", {grouping(), stmt_prints(), flatten(), bytecode(), extract_bytecode}, parser()}; + trieste::Reader reader{ + "verona_dyn", + {grouping(), call_stmts(), flatten(), bytecode(), extract_bytecode}, + parser()}; trieste::Driver driver{reader, &options}; auto build_res = driver.run(argc, argv); - if (build_res == 0 && result->has_value()) { + if (build_res == 0 && result->has_value()) + { verona::interpreter::start(result->value(), options.iterative); } return build_res; diff --git a/src/lang/lang.h b/src/lang/lang.h new file mode 100644 index 0000000..d33ad1d --- /dev/null +++ b/src/lang/lang.h @@ -0,0 +1,92 @@ +#pragma once + +#include "bytecode.h" +#include "trieste/trieste.h" + +using namespace trieste; + +#define TAB_SIZE 4 + +inline const TokenDef Ident{"ident", trieste::flag::print}; +inline const TokenDef Assign{"assign"}; +inline const TokenDef Create{"create"}; +inline const TokenDef For{"for"}; +inline const TokenDef If{"if"}; +inline const TokenDef Else{"else"}; +inline const TokenDef Block{"block"}; +inline const TokenDef Empty{"empty"}; +inline const TokenDef Drop{"drop"}; +inline const TokenDef Freeze{"freeze"}; +inline const TokenDef Region{"region"}; +inline const TokenDef Lookup{"lookup"}; +inline const TokenDef Parens{"parens"}; + +inline const TokenDef Op{"op"}; +inline const TokenDef Rhs{"rhs"}; +inline const TokenDef Lhs{"lhs"}; +inline const TokenDef Key{"key"}; +inline const TokenDef Value{"value"}; +inline const TokenDef Compile{"compile"}; + +namespace verona::wf +{ + inline const auto lv = Ident | Lookup; + inline const auto rv = lv | Empty | Null | String | Create | Call; + inline const auto cmp_values = Ident | Lookup | Null; + inline const auto key = Ident | Lookup | String; + + inline const auto grouping = (Top <<= File) | (File <<= Body) | + (Body <<= Block) | + (Block <<= + (Freeze | Region | Assign | If | For | Func | Return | ReturnValue | + Call)++) | + (Assign <<= (Lhs >>= lv) * (Rhs >>= rv)) | + (Lookup <<= (Lhs >>= lv) * (Rhs >>= key)) | (Region <<= Ident) | + (Freeze <<= Ident) | (Create <<= Ident) | (If <<= Eq * Block * Block) | + (For <<= (Key >>= Ident) * (Value >>= Ident) * (Op >>= lv) * Block) | + (Eq <<= (Lhs >>= cmp_values) * (Rhs >>= cmp_values)) | + (Func <<= Ident * Params * Body) | (Call <<= (Op >>= key) * List) | + (ReturnValue <<= rv) | (List <<= rv++) | (Params <<= Ident++); + + inline const trieste::wf::Wellformed bytecode = (Top <<= Body) | + (Body <<= + (LoadFrame | StoreFrame | LoadField | StoreField | Drop | Null | + CreateObject | CreateRegion | FreezeObject | IterNext | Print | Eq | Neq | + Jump | JumpFalse | Label | Call | Return | ReturnValue | ClearStack)++) | + (CreateObject <<= (Dictionary | String | KeyIter | Proto | Func)) | + (Func <<= Body) | (Label <<= Ident)[Ident]; +} + +inline const auto LV = T(Ident, Lookup); +inline const auto RV = T(Empty, Ident, Lookup, Null, String, Create, Call); +inline const auto CMP_V = T(Ident, Lookup, Null); +inline const auto KEY = T(Ident, Lookup, String); + +// Parsing && AST construction +Parse parser(); +PassDef grouping(); + +// Lowering && Bytecode +PassDef call_stmts(); +PassDef flatten(); +PassDef bytecode(); + +inline Node create_print(size_t line, std::string text) +{ + std::stringstream ss; + ss << "Line " << line << ": " << text; + return NodeDef::create(Print, ss.str()); +} +inline Node create_print(Node from, std::string text) +{ + auto [line, col] = from->location().linecol(); + return create_print(line + 1, text); +} +inline Node create_print(Node from) +{ + return create_print(from, std::string(from->location().view())); +} +inline Node create_from(Token def, Node from) +{ + return NodeDef::create(def, from->location()); +} diff --git a/src/lang/passes/bytecode.cc b/src/lang/passes/bytecode.cc new file mode 100644 index 0000000..42f95f2 --- /dev/null +++ b/src/lang/passes/bytecode.cc @@ -0,0 +1,105 @@ +#include "../lang.h" + +PassDef bytecode() +{ + PassDef p{ + "bytecode", + verona::wf::bytecode, + dir::topdown, + { + T(File) << T(Body)[Body] >> + [](auto& _) { return Body << (Compile << *_[Body]); }, + T(Compile) << (T(Body)[Body] << Any++[Block]) >> + [](auto& _) { + return create_from(Body, _(Body)) << (Compile << _[Block]); + }, + + T(Compile) << (Any[Lhs] * (Any * Any++)[Rhs]) >> + [](auto& _) { + return Seq << (Compile << _[Lhs]) << (Compile << _[Rhs]); + }, + + // The node doesn't require additional processing and should be copied + T(Compile) << T( + Null, + Label, + Print, + Jump, + JumpFalse, + CreateObject, + StoreFrame, + LoadFrame, + IterNext, + StoreField, + Return, + ReturnValue, + ClearStack)[Op] >> + [](auto& _) -> Node { return _(Op); }, + + T(Compile) << (T(Eq, Neq)[Op] << (Any[Lhs] * Any[Rhs])) >> + [](auto& _) { + return Seq << (Compile << _(Lhs)) << (Compile << _(Rhs)) + << _(Op)->type(); + }, + + T(Compile) << (T(Ident)[Ident]) >> + [](auto& _) { return create_from(LoadFrame, _(Ident)); }, + + T(Compile) << (T(Lookup)[Lookup] << (Any[Op] * Any[Key] * End)) >> + [](auto& _) { + return Seq << (Compile << _[Op]) << (Compile << _[Key]) + << create_from(LoadField, _(Lookup)); + }, + + T(Compile) << (T(Assign)[Op] << (T(Ident)[Ident] * Any[Rhs])) >> + [](auto& _) { + return Seq << (Compile << _[Rhs]) << create_from(StoreFrame, _(Ident)) + << create_print(_(Op)); + }, + + T(Compile) + << (T(Assign)[Assign] + << ((T(Lookup)[Lookup] << (Any[Op] * Any[Key] * End)) * + Any[Rhs])) >> + [](auto& _) { + return Seq << (Compile << _[Op]) << (Compile << _[Key]) + << (Compile << _[Rhs]) + << create_from(StoreField, _(Lookup)) + << create_print(_(Assign)); + }, + + T(Compile) << (T(Freeze)[Op] << T(Ident)[Ident]) >> + [](auto& _) { + return Seq << (Compile << _[Ident]) << FreezeObject + << create_print(_(Op)); + }, + + T(Compile) << (T(Create)[Op] << T(Ident)[Ident]) >> + [](auto& _) { + return Seq << (Compile << _[Ident]) << (CreateObject << Proto); + }, + + T(Compile) << (T(Region)[Op] << T(Ident)[Ident]) >> + [](auto& _) { + return Seq << (Compile << _[Ident]) << CreateRegion + << create_print(_(Op)); + }, + + T(Compile) + << (T(Call)[Call] << (KEY[Op] * (T(List) << Any++[List]) * End)) >> + [](auto& _) { + // The print is done by the called function + return Seq << (Compile << _[List]) << (Compile << _(Op)) + << (Call ^ std::to_string(_[List].size())); + }, + + T(Compile) << End >> [](auto&) -> Node { return {}; }, + + T(Compile) << (T(Empty)) >> + [](auto&) -> Node { return CreateObject << Dictionary; }, + T(Compile) << (T(String)[String]) >> + [](auto& _) -> Node { return CreateObject << _(String); }, + + }}; + return p; +} diff --git a/src/lang/passes/call_stmts.cc b/src/lang/passes/call_stmts.cc new file mode 100644 index 0000000..2e3d59f --- /dev/null +++ b/src/lang/passes/call_stmts.cc @@ -0,0 +1,25 @@ +#include "../lang.h" + +namespace verona::wf +{ + using namespace trieste::wf; + + inline const auto call_stmts = grouping | + (Block <<= + (Freeze | Region | Assign | If | For | Func | Return | ReturnValue | Call | + ClearStack | Print)++); +} + +PassDef call_stmts() +{ + return { + "call_stmts", + verona::wf::call_stmts, + dir::bottomup | dir::once, + { + In(Block) * T(Call)[Call] >> + [](auto& _) { + return Seq << _(Call) << ClearStack << create_print(_(Call)); + }, + }}; +} diff --git a/src/lang/passes/flatten.cc b/src/lang/passes/flatten.cc new file mode 100644 index 0000000..3beb5c2 --- /dev/null +++ b/src/lang/passes/flatten.cc @@ -0,0 +1,160 @@ +#include "../lang.h" + +namespace verona::wf +{ + using namespace trieste::wf; + inline const trieste::wf::Wellformed flatten = (Top <<= File) | + (File <<= Body) | + (Body <<= + (Freeze | Region | Assign | Eq | Neq | Label | Jump | JumpFalse | Print | + StoreFrame | LoadFrame | CreateObject | Ident | IterNext | Create | + StoreField | Lookup | String | Call | Return | ReturnValue | + ClearStack)++) | + (CreateObject <<= (KeyIter | String | Dictionary | Func)) | + (Func <<= Compile) | (Compile <<= Body) | (Create <<= Ident) | + (Assign <<= (Lhs >>= lv) * (Rhs >>= rv)) | + (Lookup <<= (Lhs >>= lv) * (Rhs >>= key)) | (Region <<= Ident) | + (Freeze <<= Ident) | (Call <<= (Op >>= key) * List) | (List <<= rv++) | + (Params <<= Ident++) | + (Eq <<= (Lhs >>= cmp_values) * (Rhs >>= cmp_values)) | + (Neq <<= (Lhs >>= cmp_values) * (Rhs >>= cmp_values)) | + (Label <<= Ident)[Ident]; + ; +} // namespace verona::wf + +int g_jump_label_counter = 0; +std::string new_jump_label() +{ + g_jump_label_counter += 1; + return "label_" + std::to_string(g_jump_label_counter); +} + +int g_iter_name = 0; +std::string new_iter_name() +{ + g_iter_name += 1; + return "_iter_" + std::to_string(g_iter_name); +} + +std::string expr_header(Node expr) +{ + auto expr_str = expr->location().view(); + return std::string(expr_str.substr(0, expr_str.find(":") + 1)); +} + +PassDef flatten() +{ + return { + "flatten", + verona::wf::flatten, + dir::bottomup, + { + In(Body) * (T(Block) << Any++[Block]) >> + [](auto& _) { return Seq << _[Block]; }, + T(If)[If] + << (T(Eq)[Op] * (T(Block) << Any++[Lhs]) * + (T(Block) << Any++[Rhs])) >> + [](auto& _) { + auto else_label = new_jump_label(); + auto join_label = new_jump_label(); + + auto if_head = expr_header(_(If)); + + return Seq << _(Op) + << (JumpFalse ^ else_label) + // Then block + << create_print(_(If), if_head + " (True)") << _[Lhs] + << (Jump ^ join_label) + // Else block + << ((Label ^ "else:") << (Ident ^ else_label)) + << create_print(_(If), if_head + " (False)") + << _[Rhs] + // Join + << (Label << (Ident ^ join_label)); + }, + T(For)[For] + << (T(Ident)[Key] * T(Ident)[Value] * LV[Op] * + (T(Block) << Any++[Block]) * End) >> + [](auto& _) { + auto it_name = new_iter_name(); + + auto start_label = new_jump_label(); + auto break_label = new_jump_label(); + + auto for_head = expr_header(_(For)); + + return Seq + // Prelude + << clone(_(Op)) << (CreateObject << KeyIter) + << (StoreFrame ^ it_name) << (Ident ^ it_name) + << (String ^ "source") << _(Op) << (StoreField) + << create_print(_(For), "create " + it_name) + << ((Label ^ "start:") << (Ident ^ start_label)) + // key = iter++ + << (Ident ^ it_name) << (IterNext) + << create_from(StoreFrame, _(Key)) + // While (key != null) + << (Neq << create_from(Ident, _(Key)) << Null) + << (JumpFalse ^ break_label) + // value = it.source.key + << (Lookup << (Lookup << (Ident ^ it_name) << (String ^ "source")) + << create_from(Ident, _(Key))) + << create_from(StoreFrame, _(Value)) + << create_print(_(For), for_head + " (Next)") + // Block + << _[Block] + // Final cleanup + << (Jump ^ start_label) + << ((Label ^ "break:") << (Ident ^ break_label)) + << create_print(_(For), for_head + " (Break)") + << ((Assign ^ ("drop " + std::string(_(Value)->location().view()))) + << create_from(Ident, _(Value)) << Null) + << ((Assign ^ ("drop " + it_name)) << (Ident ^ it_name) << Null); + }, + + T(Func)[Func] + << (T(Ident)[Ident] * T(Params)[Params] * + (T(Body)[Body] << Any++[Block]) * End) >> + [](auto& _) { + auto func_head = expr_header(_(Func)); + + // Function setup + Node body = Body; + Node args = _(Params); + for (auto it = args->begin(); it != args->end(); it++) + { + body << create_from(StoreFrame, *it); + } + body << create_print(_(Func), func_head + " (Enter)"); + + // Function body + auto block = _[Block]; + for (auto stmt : block) + { + if (stmt == ReturnValue) + { + body + << (create_from(Assign, stmt) + << (Ident ^ "return") << stmt->at(0)); + body << (LoadFrame ^ "return"); + body << create_from(ReturnValue, stmt); + } + else if (stmt == Return) + { + body << create_print(stmt); + body << stmt; + } + else + { + body << stmt; + } + } + body << create_print(_(Func), func_head + " (Exit)"); + + // Function cleanup + return Seq << (CreateObject << (Func << (Compile << body))) + << (StoreFrame ^ _(Ident)) + << create_print(_(Func), func_head); + }, + }}; +} diff --git a/src/lang/passes/grouping.cc b/src/lang/passes/grouping.cc new file mode 100644 index 0000000..38bb55d --- /dev/null +++ b/src/lang/passes/grouping.cc @@ -0,0 +1,110 @@ +#include "../lang.h" + +PassDef grouping() +{ + PassDef p{ + "grouping", + verona::wf::grouping, + dir::bottomup, + { + // Normalize so that all expressions are inside bodies and blocks. + T(File) << (--T(Body) * Any++[File]) >> + [](auto& _) { return File << (Body << (Block << _[File])); }, + + In(Group) * LV[Lhs] * (T(Lookup)[Lookup] << (T(Group) << KEY[Rhs])) >> + [](auto& _) { return Lookup << _[Lhs] << _(Rhs); }, + + T(Group) << ((T(Region)[Region] << End) * T(Ident)[Ident] * End) >> + [](auto& _) { + _(Region)->extend(_(Ident)->location()); + return _(Region) << _(Ident); + }, + + T(Group) << ((T(Freeze)[Freeze] << End) * T(Ident)[Ident] * End) >> + [](auto& _) { + _(Freeze)->extend(_(Ident)->location()); + return _(Freeze) << _(Ident); + }, + + T(Group) << ((T(Drop)[Drop] << End) * LV[Lhs] * End) >> + [](auto& _) { return Assign << _(Lhs) << Null; }, + // function(arg, arg) + --In(Func) * + (T(Group)[Group] + << ((T(Ident)[Ident]) * (T(Parens)[Parens] << (~T(List)[List])) * + End)) >> + [](auto& _) { + auto list = _(List); + if (!list) + { + list = create_from(List, _(Parens)); + } + + return create_from(Call, _(Group)) << _(Ident) << list; + }, + + T(Group) << ((T(Create)[Create] << End) * T(Ident)[Ident] * End) >> + [](auto& _) { + _(Create)->extend(_(Ident)->location()); + return Group << (_(Create) << _(Ident)); + }, + + T(Assign) + << ((T(Group) << LV[Lhs] * End) * + ((T(Group) << (RV[Rhs] * End)) / (RV[Rhs] * End)) * End) >> + [](auto& _) { return Assign << _[Lhs] << _[Rhs]; }, + T(Eq) + << ((T(Group) << CMP_V[Lhs] * End) * (T(Group) << CMP_V[Rhs] * End) * + End) >> + [](auto& _) { return Eq << _[Lhs] << _[Rhs]; }, + + (T(If) << (T(Group) * T(Eq)[Eq] * (T(Group) << T(Block)[Block]))) >> + [](auto& _) { return If << _(Eq) << _(Block); }, + (T(Else) << (T(Group) * (T(Group) << T(Block)[Block]))) >> + [](auto& _) { return Else << _(Block); }, + (T(If)[If] << (T(Eq) * T(Block) * End)) * (T(Else) << T(Block)[Block]) >> + [](auto& _) { return _(If) << _(Block); }, + (T(If)[If] << (T(Eq) * T(Block) * End)) * (--T(Else)) >> + [](auto& _) { + // This adds an empty else block, if no else was written + return _(If) << Block; + }, + + In(List) * (T(Group) << (RV[Rhs] * End)) >> + [](auto& _) { return _(Rhs); }, + + T(For)[For] + << ((T(Group)) * + (T(List) << (T(Ident)[Key] * T(Ident)[Value] * End)) * + (T(Group) << (LV[Op] * End)) * + (T(Group) << (T(Block)[Block] * End)) * End) >> + [](auto& _) { + // This function only has a block for now, as a lowering pass still + // needs to do some modifications until it's a "full" body. + return create_from(For, _(For)) + << _(Key) << _(Value) << _(Op) << _(Block); + }, + + T(Func)[Func] + << ((T(Group) << End) * + (T(Group) + << ((T(Ident)[Ident]) * + (T(Parens)[Parens] << (~(T(List) << T(Ident)++[List]))) * + End)) * + (T(Group) << T(Block)[Block]) * End) >> + [](auto& _) { + return create_from(Func, _(Func)) + << _(Ident) << (create_from(Params, _(Parens)) << _[List]) + << (Body << _(Block)); + }, + + T(Return)[Return] << ((T(Group) << End) * End) >> + [](auto& _) { return create_from(Return, _(Return)); }, + T(Return)[Return] + << ((T(Group) << End) * (T(Group) << (RV[Rhs] * End)) * End) >> + [](auto& _) { return create_from(ReturnValue, _(Return)) << _(Rhs); }, + + }}; + + return p; +} diff --git a/src/lang/passes/parse.cc b/src/lang/passes/parse.cc new file mode 100644 index 0000000..3cc5090 --- /dev/null +++ b/src/lang/passes/parse.cc @@ -0,0 +1,177 @@ +#include "../lang.h" + +namespace verona::wf +{ + using namespace trieste::wf; + + inline const auto parse_tokens = Region | Ident | Lookup | Empty | Freeze | + Drop | Null | String | Create | Parens; + inline const auto parse_groups = + Group | Assign | If | Else | Block | For | Func | List | Return; + + inline const auto parser = (Top <<= File) | (File <<= parse_groups++) | + (Assign <<= Group++) | (If <<= Group * Eq * Group) | + (Else <<= Group * Group) | (Group <<= (parse_tokens | Block | List)++) | + (Block <<= (parse_tokens | parse_groups)++) | (Eq <<= Group * Group) | + (Lookup <<= Group) | (For <<= Group * List * Group * Group) | + (List <<= Group++) | (Parens <<= (Group | List)++) | + (Func <<= Group * Group * Group) | (Return <<= Group++); +} + +struct Indent +{ + size_t indent; + Token toc; +}; + +trieste::Parse parser() +{ + Parse p{depth::file, verona::wf::parser}; + + auto indent = std::make_shared>(); + indent->push_back({0, File}); + + auto update_indent = [indent](detail::Make& m, size_t this_indent) { + m.term({Assign, Return}); + + if (this_indent > indent->back().indent) + { + std::cout << "this_indent " << this_indent << std::endl; + std::cout << "indent->back().indent " << indent->back().indent + << std::endl; + m.error("unexpected indention"); + } + + while (this_indent < indent->back().indent) + { + m.term({Block, Group, indent->back().toc}); + indent->pop_back(); + } + + if (this_indent != indent->back().indent) + { + m.error("unexpected indention"); + } + }; + + p("start", + { + // Empty lines should be ignored + "[ \\t]*\\r?\\n" >> [](auto&) {}, + + // Line comment + "(?:#[^\\n]*)" >> [](auto&) {}, + + // Indention + " +" >> + [update_indent](auto& m) { + if (m.match().len % TAB_SIZE != 0) + { + m.error("unexpected indention"); + } + auto this_indent = m.match().len / TAB_SIZE; + update_indent(m, this_indent); + m.mode("contents"); + }, + + "\\t*" >> + [update_indent](auto& m) { + auto this_indent = m.match().len; + update_indent(m, this_indent); + m.mode("contents"); + }, + }); + + p("contents", + { + // Indentation + "\\r?\\n" >> [](auto& m) { m.mode("start"); }, + + "[[:blank:]]+" >> [](auto&) {}, + + // Line comment + "(?:#[^\\n\\r]*)" >> [](auto&) {}, + + "def" >> [](auto& m) { m.seq(Func); }, + "\\(" >> [](auto& m) { m.push(Parens); }, + "\\)" >> + [](auto& m) { + m.term({List, Parens}); + m.extend(Parens); + }, + "return" >> [](auto& m) { m.seq(Return); }, + + "for" >> [](auto& m) { m.seq(For); }, + "in" >> + [](auto& m) { + // In should always be in a list from the identifiers. + m.term({List}); + }, + "," >> [](auto& m) { m.seq(List); }, + + "if" >> [](auto& m) { m.seq(If); }, + "else" >> [](auto& m) { m.seq(Else); }, + ":" >> + [indent](auto& m) { + // Exit conditionals expressions. + m.term({Eq}); + + Token toc = Empty; + if (m.in(If)) + { + toc = If; + } + else if (m.in(Else)) + { + toc = Else; + } + else if (m.in(For)) + { + toc = For; + } + else if (m.in(Func)) + { + toc = Func; + } + else + { + m.error("unexpected colon"); + return; + } + assert(toc != Empty); + + auto current_indent = indent->back().indent; + auto next_indent = current_indent + 1; + indent->push_back({next_indent, toc}); + + if (m.in(Group)) + { + m.pop(Group); + } + + m.push(Block); + }, + "drop" >> [](auto& m) { m.add(Drop); }, + "create" >> [](auto& m) { m.add(Create); }, + "freeze" >> [](auto& m) { m.add(Freeze); }, + "region" >> [](auto& m) { m.add(Region); }, + "None" >> [](auto& m) { m.add(Null); }, + "[0-9A-Za-z_]+" >> [](auto& m) { m.add(Ident); }, + "\\[" >> [](auto& m) { m.push(Lookup); }, + "\\]" >> [](auto& m) { m.term({Lookup}); }, + "\\.([0-9A-Za-z_]+)" >> + [](auto& m) { + m.push(Lookup); + m.add(String, 1); + m.term({Lookup}); + }, + "\"([^\\n\"]+)\"" >> [](auto& m) { m.add(String, 1); }, + "==" >> [](auto& m) { m.seq(Eq); }, + "=" >> [](auto& m) { m.seq(Assign); }, + "{}" >> [](auto& m) { m.add(Empty); }, + }); + + p.done([update_indent](auto& m) { update_indent(m, 0); }); + + return p; +} diff --git a/src/main.cc b/src/main.cc index 47fa1e8..4a22e1d 100644 --- a/src/main.cc +++ b/src/main.cc @@ -2,9 +2,9 @@ * This file contains a model implementation of region tracking. */ -int load_trieste(int argc, char **argv); +int load_trieste(int argc, char** argv); -int main(int argc, char **argv) +int main(int argc, char** argv) { return load_trieste(argc, argv); } diff --git a/src/rt/core.h b/src/rt/core.h new file mode 100644 index 0000000..d3c86e5 --- /dev/null +++ b/src/rt/core.h @@ -0,0 +1,150 @@ +#include "objects/dyn_object.h" +#include "rt.h" + +namespace rt::core +{ + + class PrototypeObject : public objects::DynObject + { + std::string name; + + public: + PrototypeObject(std::string name_, objects::DynObject* prototype = nullptr) + : objects::DynObject(prototype, true), name(name_) + {} + + std::string get_name() + { + std::stringstream stream; + stream << "[" << name << "]"; + return stream.str(); + } + }; + + PrototypeObject framePrototypeObject{"Frame"}; + + class FrameObject : public objects::DynObject + { + FrameObject() : objects::DynObject(&framePrototypeObject, true) {} + + public: + FrameObject(objects::DynObject* parent_frame) + : objects::DynObject(&framePrototypeObject) + { + if (parent_frame) + { + auto old_value = this->set(objects::ParentField, parent_frame); + add_reference(this, parent_frame); + assert(!old_value); + } + } + + static FrameObject* create_first_stack() + { + return new FrameObject(); + } + }; + + PrototypeObject funcPrototypeObject{"Function"}; + PrototypeObject bytecodeFuncPrototypeObject{ + "BytecodeFunction", &funcPrototypeObject}; + + class FuncObject : public objects::DynObject + { + public: + FuncObject(objects::DynObject* prototype_, bool global = false) + : objects::DynObject(prototype_, global) + {} + }; + + class BytecodeFuncObject : public FuncObject + { + verona::interpreter::Bytecode* body; + + public: + BytecodeFuncObject(verona::interpreter::Bytecode* body_) + : FuncObject(&bytecodeFuncPrototypeObject), body(body_) + {} + ~BytecodeFuncObject() + { + verona::interpreter::delete_bytecode(this->body); + this->body = nullptr; + } + + verona::interpreter::Bytecode* get_bytecode() + { + return this->body; + } + }; + + PrototypeObject stringPrototypeObject{"String"}; + + class StringObject : public objects::DynObject + { + std::string value; + + public: + StringObject(std::string value_, bool global = false) + : objects::DynObject(&stringPrototypeObject, global), value(value_) + {} + + std::string get_name() + { + std::stringstream stream; + stream << "\"" << value << "\""; + return stream.str(); + } + + std::string as_key() + { + return value; + } + + objects::DynObject* is_primitive() + { + return this; + } + }; + + StringObject TrueObject{"True", true}; + StringObject FalseObject{"False", true}; + + // The prototype object for iterators + // TODO put some stuff in here? + PrototypeObject keyIterPrototypeObject{"KeyIterator"}; + + class KeyIterObject : public objects::DynObject + { + std::map::iterator iter; + std::map::iterator iter_end; + + public: + KeyIterObject(std::map& fields) + : objects::DynObject(&keyIterPrototypeObject), + iter(fields.begin()), + iter_end(fields.end()) + {} + + objects::DynObject* iter_next() + { + objects::DynObject* obj = nullptr; + if (this->iter != this->iter_end) + { + obj = make_str(this->iter->first); + this->iter++; + } + + return obj; + } + + std::string get_name() + { + return ""; + } + + objects::DynObject* is_primitive() + { + return this; + } + }; +} // namespace rt::core diff --git a/src/rt/mermaid.h b/src/rt/mermaid.h deleted file mode 100644 index fc6ddbc..0000000 --- a/src/rt/mermaid.h +++ /dev/null @@ -1,115 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "objects.h" - -namespace objects { - -void mermaid(std::vector &roots, std::ostream &out) { - // Give a nice id to each object. - std::map visited; - // Keep track of all the objects in a region. - std::map> region_strings; - // Keep track of the immutable objects. - std::vector immutable_objects; - // // Add frame as local region - // visited[DynObject::frame()] = 0; - // region_strings[DynObject::get_local_region()] = {0}; - // Add nullptr as immutable - visited[nullptr] = 0; - immutable_objects.push_back(0); - // Account for frame and nullptr objects. - size_t id = 1; // was 2 - // Header - out << "```mermaid" << std::endl; - out << "graph TD" << std::endl; - - bool unreachable = false; - - auto explore = [&](Edge e) { - DynObject *dst = e.target; - std::string key = e.key; - DynObject *src = e.src; - if (src != nullptr) { - out << " id" << visited[src] << " -->|" << key << "| "; - } - if (visited.find(dst) != visited.end()) { - out << "id" << visited[dst] << std::endl; - return false; - } - auto curr_id = id++; - visited[dst] = curr_id; - out << "id" << curr_id << "[ "; - out << dst->get_name(); - out << "
rc=" << dst->rc; - - out << " ]" << (unreachable ? ":::unreachable" : "") << std::endl; - - auto region = DynObject::get_region(dst); - if (region != nullptr) { - region_strings[region].push_back(curr_id); - } - - if (dst->is_immutable()) { - immutable_objects.push_back(curr_id); - } - return true; - }; - // Output all reachable edges - for (auto &root : roots) { - visit({root.src, root.key, root.target}, explore); - } - - // Output the unreachable parts of the graph - unreachable = true; - for (auto &root : DynObject::all_objects) { - visit({nullptr, "", root}, explore); - } - - // Output any region parent edges. - for (auto [region, objects] : region_strings) { - if (region->parent == nullptr) - continue; - out << " region" << region->parent << " <-.-o region" << region - << std::endl; - } - - // Output all the region membership information - for (auto [region, objects] : region_strings) { - out << "subgraph "; - - if (region == DynObject::get_local_region()) { - out << "local region" << std::endl; - } else { - out << std::endl; - out << " region" << region << "[\\" << region - << "
lrc=" << region->local_reference_count - << "
sbrc=" << region->sub_region_reference_count - << "
prc=" << region->parent_reference_count << "/]" << std::endl; - } - for (auto obj : objects) { - out << " id" << obj << std::endl; - } - out << "end" << std::endl; - } - - // Output the immutable region. - out << "subgraph Immutable" << std::endl; - out << " id0[nullptr]" << std::endl; - for (auto obj : immutable_objects) { - out << " id" << obj << std::endl; - } - out << "end" << std::endl; - - // Output object count as very useful. - out << "subgraph Count " << DynObject::get_count() << std::endl; - out << "end" << std::endl; - out << "classDef unreachable stroke:red,stroke-width:2px" << std::endl; - // Footer (end of mermaid graph) - out << "```" << std::endl; -} -} // namespace objects \ No newline at end of file diff --git a/src/rt/nop.h b/src/rt/nop.h deleted file mode 100644 index f4875dd..0000000 --- a/src/rt/nop.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -namespace utils -{ - template - class Nop - { - public: - void operator()(T) const { } - }; -} \ No newline at end of file diff --git a/src/rt/objects.h b/src/rt/objects.h deleted file mode 100644 index a9f2e89..0000000 --- a/src/rt/objects.h +++ /dev/null @@ -1,551 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "nop.h" -#include "region.h" -#include "rt.h" -#include "tagged_pointer.h" -#include "../lang/interpreter.h" - -namespace objects { -constexpr uintptr_t ImmutableTag{1}; -const std::string PrototypeField{" proto "}; -const std::string ParentField{" parent "}; - -using NopDO = utils::Nop; - -template -inline void visit(Edge e, Pre pre, Post post = {}); - -template -inline void visit(DynObject* start, Pre pre, Post post = {}); - -// Representation of objects -class DynObject { - friend class Reference; - friend DynObject* make_iter(DynObject *obj); - friend void mermaid(std::vector &roots, std::ostream &out); - friend void destruct(DynObject *obj); - friend void dealloc(DynObject *obj); - template - friend void visit(Edge, Pre, Post); - - thread_local static std::vector frame_stack; - - // Represents the region of specific object. Uses small pointers to - // encode special regions. - using RegionPointer = utils::TaggedPointer; - - - // TODO: Not concurrency safe - inline static size_t count{0}; - // TODO: Not concurrency safe - inline static std::set all_objects{}; - - size_t rc{1}; - RegionPointer region{nullptr}; - DynObject* prototype{nullptr}; - - std::map fields{}; - - static Region *get_region(DynObject *obj) { - if ((obj == nullptr) || obj->is_immutable()) - return nullptr; - return obj->region.get_ptr(); - } - - bool is_local_object() { return region.get_ptr() == get_local_region(); } - - - static void remove_region_reference(Region *src, Region *target) { - if (src == target) { - std::cout << "Same region, no need to do anything" << std::endl; - return; - } - - // Handle immutable case - if (target == nullptr) - return; - - if (src == get_local_region()) { - Region::dec_lrc(target); - return; - } - - assert(target->parent == src); - Region::dec_prc(target); - return; - } - - static void add_region_reference(Region *src_region, DynObject *target) { - if (target->is_immutable()) - return; - - auto target_region = get_region(target); - if (src_region == target_region) - return; - - if (src_region == get_local_region()) { - Region::inc_lrc(target_region); - return; - } - - if (target_region == get_local_region()) { - target->add_to_region(src_region); - return; - } - - Region::set_parent(target_region, src_region); - } - - size_t change_rc(signed delta) { - std::cout << "Change RC: " << get_name() << " " << rc << " + " << delta - << std::endl; - if (!is_immutable()) { - assert(delta == 0 || rc != 0); - rc += delta; - // Check not underflowing. - assert(rc >> 62 == 0); - return rc; - } - - // TODO Follow SCC chain to the root. - // Have to use atomic as this can be called from multiple threads. - return std::atomic_ref(rc).fetch_add(delta, std::memory_order_relaxed) + - delta; - } - - void add_to_region(Region *r) { - size_t internal_references{0}; - size_t rc_of_added_objects{0}; - visit(this, [&](Edge e) { - auto obj = e.target; - if (obj == nullptr || obj->is_immutable()) - return false; - - if (obj->is_local_object()) { - std::cout << "Adding object to region: " << obj->get_name() - << " rc = " << obj->rc << std::endl; - rc_of_added_objects += obj->rc; - internal_references++; - obj->region = {r}; - get_local_region()->objects.erase(obj); - r->objects.insert(obj); - return true; - } - - auto obj_region = get_region(obj); - if (obj_region == r) { - std::cout << "Adding internal reference to object: " << obj->get_name() - << std::endl; - internal_references++; - return false; - } - - Region::set_parent(obj_region, r); - Region::dec_lrc(obj_region); - return false; - }); - r->local_reference_count += rc_of_added_objects - internal_references; - - std::cout << "Added " << rc_of_added_objects - internal_references - << " to LRC of region" << std::endl; - std::cout << "Region LRC: " << r->local_reference_count << std::endl; - std::cout << "Internal references found: " << internal_references - << std::endl; - } - -public: - // prototype is borrowed, the caller does not need to provide an RC. - DynObject(DynObject* prototype_ = nullptr, bool global = false) : prototype(prototype_) { - if (!global) { - count++; - all_objects.insert(this); - auto local_region = get_local_region(); - region = local_region; - local_region->objects.insert(this); - } - if (prototype != nullptr) - prototype->change_rc(1); - std::cout << "Allocate: " << get_name() << std::endl; - } - - // TODO This should use prototype lookup for the destructor. - virtual ~DynObject() { - // Erase from set of all objects, and remove count if found. - auto matched = all_objects.erase(this); - count -= matched; - - // If it wasn't in the all_objects set, then it was a special object - // that we don't track for leaks, otherwise, we need to check if the - // RC is zero. - if (change_rc(0) != 0 && matched != 0) { - error("Object still has references"); - } - - auto r = get_region(this); - if (!is_immutable() && r != nullptr) - r->objects.erase(this); - - std::cout << "Deallocate: " << get_name() << std::endl; - } - - /// @brief The string representation of this value to - /// TODO remove virtual once we have primitive functions. - virtual std::string get_name() - { - std::stringstream stream; - stream << this; - return stream.str(); - } - - /// TODO remove virtual once we have primitive functions. - virtual DynObject* is_primitive() { - return nullptr; - } - - inline static void push_frame(DynObject* frame) { - frame_stack.push_back(frame); - } - - // Place holder for the frame object. Used in various places if we don't have - // an entry point. - inline static DynObject *frame() { - return frame_stack.back(); - } - - inline static void pop_frame() { - auto frame = frame_stack.back(); - frame_stack.pop_back(); - remove_reference(frame_stack.back(), frame); - } - - void freeze() { - // TODO SCC algorithm - visit(this, [](Edge e) { - auto obj = e.target; - if (obj->is_immutable()) - return false; - - auto r = get_region(obj); - if (r != nullptr) - { - get_region(obj)->objects.erase(obj); - } - obj->region.set_tag(ImmutableTag); - return true; - }); - } - - - bool is_immutable() { return region.get_tag() == ImmutableTag; } - - [[nodiscard]] DynObject *get(std::string name) { - auto result = fields.find(name); - if (result != fields.end()) - return result->second; - - if (name == PrototypeField) - return prototype; - - // Search the prototype chain. - // TODO make this iterative. - if (prototype != nullptr) - return prototype->get(name); - - // No field or prototype chain found. - return nullptr; - } - - [[nodiscard]] DynObject *set(std::string name, DynObject *value) { - if (is_immutable()) { - error("Cannot mutate immutable object"); - } - DynObject *old = fields[name]; - fields[name] = value; - return old; - } - - // The caller must provide an rc for value. - [[nodiscard]] DynObject* set_prototype(DynObject* value) { - if (is_immutable()) { - error("Cannot mutate immutable object"); - } - DynObject* old = prototype; - prototype = value; - return old; - } - - DynObject* get_prototype() { - return prototype; - } - - - static void add_reference(DynObject *src, DynObject *target) { - if (target == nullptr) - return; - - target->change_rc(1); - - auto src_region = get_region(src); - add_region_reference(src_region, target); - } - - static void remove_reference(DynObject *src_initial, - DynObject *old_dst_initial) { - visit( - {src_initial, "", old_dst_initial}, - [&](Edge e) { - if (e.target == nullptr) - return false; - - std::cout << "Remove reference from: " << e.src->get_name() << " to " - << e.target->get_name() << std::endl; - bool result = e.target->change_rc(-1) == 0; - - remove_region_reference(get_region(e.src), get_region(e.target)); - return result; - }, - [&](DynObject *obj) { delete obj; }); - - Region::collect(); - } - - static void move_reference(DynObject *src, DynObject *dst, - DynObject *target) { - if (target == nullptr || target->is_immutable()) - return; - - auto src_region = get_region(src); - auto dst_region = get_region(dst); - - if (src_region == dst_region) - return; - - auto target_region = get_region(target); - - add_region_reference(dst_region, target); - // Note that target_region and get_region(target) are not necessarily the same. - remove_region_reference(src_region, target_region); - } - - void create_region() { - Region *r = new Region(); - add_to_region(r); - // Add root reference as external. - r->local_reference_count++; - } - - static size_t get_count() { return count; } - - static void set_local_region(Region *r) { frame()->region = {r}; } - - static Region *get_local_region() { - auto frame = DynObject::frame(); - if (frame->region) { - return frame->region.get_ptr(); - } - - return frame->get(ParentField)->get_local_region(); - } - - static std::set get_objects() { return all_objects; } -}; - -// The prototype object for strings -// TODO put some stuff in here? -DynObject stringPrototypeObject{nullptr, true}; - -class StringObject : public DynObject { - std::string value; - -public: - StringObject(std::string value_) - : DynObject(&stringPrototypeObject), value(value_) {} - - std::string get_name() { - return value; - } - - std::string as_key() { - return value; - } - - DynObject* is_primitive() { - return this; - } -}; - -// The prototype object for iterators -// TODO put some stuff in here? -DynObject keyIterPrototypeObject{nullptr, true}; - -class KeyIterObject : public DynObject { - std::map::iterator iter; - std::map::iterator iter_end; - - public: - KeyIterObject(std::map &fields) - : DynObject(&keyIterPrototypeObject), iter(fields.begin()), iter_end(fields.end()) {} - - DynObject* iter_next() { - DynObject *obj = nullptr; - if (this->iter != this->iter_end) { - obj = make_object(this->iter->first); - this->iter++; - } - - return obj; - } - - std::string get_name() { - return "<iterator>"; - } - - DynObject* is_primitive() { - return this; - } -}; - -// The prototype object for functions -// TODO put some stuff in here? -DynObject funcPrototypeObject{nullptr, true}; -// The prototype object for bytecode functions -// TODO put some stuff in here? -DynObject bytecodeFuncPrototypeObject{&funcPrototypeObject, true}; - -class FuncObject : public DynObject { -public: - FuncObject(DynObject* prototype_, bool global = false) : DynObject(prototype_, global) {} -}; - -class BytecodeFuncObject : public FuncObject { - verona::interpreter::Bytecode *body; -public: - BytecodeFuncObject(verona::interpreter::Bytecode *body_) : FuncObject(&bytecodeFuncPrototypeObject), body(body_) {} - ~BytecodeFuncObject() { - verona::interpreter::delete_bytecode(this->body); - this->body = nullptr; - } - - verona::interpreter::Bytecode* get_bytecode() { - return this->body; - } -}; - -// The prototype object for functions -// TODO put some stuff in here? -DynObject framePrototypeObject{nullptr, true}; - -class FrameObject : public DynObject { - FrameObject() : DynObject(&framePrototypeObject, true) {} -public: - FrameObject(DynObject* parent_frame) : DynObject(&framePrototypeObject) { - if (parent_frame) { - auto old_value = this->set(ParentField, parent_frame); - objects::add_reference(this, parent_frame); - assert(!old_value); - } - } - - static FrameObject* create_first_stack() { - return new FrameObject(); - } -}; - -void destruct(DynObject *obj) { - // Called from the region destructor. - // Remove all references to other objects. - // If in the same region, then just remove the RC, but don't try to collect - // as the whole region is being torndown including any potential cycles. - auto same_region = [](DynObject *src, DynObject *target) { - return DynObject::get_region(src) == DynObject::get_region(target); - }; - for (auto &[key, field] : obj->fields) { - if (field == nullptr) - continue; - if (same_region(obj,field)) { - // Same region just remove the rc, but don't try to collect. - field->change_rc(-1); - continue; - } - - auto old_value = obj->set(key, nullptr); - remove_reference(obj, old_value); - } - - if (same_region(obj, obj->prototype)) { - obj->prototype->change_rc(-1); - } else { - auto old_value = obj->set_prototype(nullptr); - remove_reference(obj, old_value); - } -} - -void dealloc(DynObject *obj) { - // Called from the region destructor. - // So remove from region if in one. - // This ensures we don't try to remove it from the set that is being iterated. - obj->region = nullptr; - delete obj; -} - -template -inline void visit(Edge e, Pre pre, Post post) { - if (!pre(e)) - return; - - constexpr bool HasPost = !std::is_same_v; - constexpr uintptr_t POST{1}; - - std::vector, std::string>> stack; - - auto visit_object = [&](DynObject *obj) { - if (obj == nullptr) - return; - if constexpr (HasPost) - stack.push_back({{obj, POST}, ""}); - // TODO This will need to depend on the type of object. - for (auto &[key, field] : obj->fields) - stack.push_back({obj, key}); - if (obj->prototype != nullptr) - stack.push_back({obj, PrototypeField}); - }; - - visit_object(e.target); - - while (!stack.empty()) { - auto [obj, key] = stack.back(); - auto obj_ptr = obj.get_ptr(); - stack.pop_back(); - - if (HasPost && obj.get_tag() == POST) { - post(obj_ptr); - continue; - } - - DynObject *next = obj_ptr->get(key); - if (pre({obj_ptr, key, next})) { - visit_object(next); - } - } -} - - -template -inline void visit(DynObject* start, Pre pre, Post post) -{ - visit(Edge{DynObject::frame(), "", start}, pre, post); -} - -} // namespace objects diff --git a/src/rt/objects/dyn_object.h b/src/rt/objects/dyn_object.h new file mode 100644 index 0000000..5306e53 --- /dev/null +++ b/src/rt/objects/dyn_object.h @@ -0,0 +1,467 @@ +#pragma once + +#include "../../lang/interpreter.h" +#include "../rt.h" +#include "region.h" +#include "visit.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rt::objects +{ + constexpr uintptr_t ImmutableTag{1}; + const std::string PrototypeField{"__proto__"}; + const std::string ParentField{"__parent__"}; + + // Representation of objects + class DynObject + { + friend class Reference; + friend objects::DynObject* rt::make_iter(objects::DynObject* obj); + friend void + rt::ui::mermaid(std::vector& roots, std::ostream& out); + friend void destruct(DynObject* obj); + friend void dealloc(DynObject* obj); + template + friend void visit(Edge, Pre, Post); + + thread_local static RegionPointer local_region; + + // TODO: Not concurrency safe + inline static size_t count{0}; + // TODO: Not concurrency safe + inline static std::set all_objects{}; + + size_t rc{1}; + RegionPointer region{nullptr}; + DynObject* prototype{nullptr}; + + std::map fields{}; + + static Region* get_region(DynObject* obj) + { + if ((obj == nullptr) || obj->is_immutable()) + return nullptr; + return obj->region.get_ptr(); + } + + bool is_local_object() + { + return region.get_ptr() == get_local_region(); + } + + static void remove_region_reference(Region* src, Region* target) + { + if (src == target) + { + std::cout << "Same region, no need to do anything" << std::endl; + return; + } + + // Handle immutable case + if (target == nullptr) + return; + + if (src == get_local_region()) + { + Region::dec_lrc(target); + return; + } + + assert(target->parent == src); + Region::dec_prc(target); + return; + } + + static void add_region_reference(Region* src_region, DynObject* target) + { + if (target->is_immutable()) + return; + + auto target_region = get_region(target); + if (src_region == target_region) + return; + + if (src_region == get_local_region()) + { + Region::inc_lrc(target_region); + return; + } + + if (target_region == get_local_region()) + { + target->add_to_region(src_region); + return; + } + + Region::set_parent(target_region, src_region); + } + + size_t change_rc(signed delta) + { + std::cout << "Change RC: " << get_name() << " " << rc << " + " << delta + << std::endl; + if (!is_immutable()) + { + assert(delta == 0 || rc != 0); + rc += delta; + // Check not underflowing. + assert(rc >> 62 == 0); + return rc; + } + + // TODO Follow SCC chain to the root. + // Have to use atomic as this can be called from multiple threads. + return std::atomic_ref(rc).fetch_add(delta, std::memory_order_relaxed) + + delta; + } + + void add_to_region(Region* r) + { + size_t internal_references{0}; + size_t rc_of_added_objects{0}; + visit(this, [&](Edge e) { + auto obj = e.target; + if (obj == nullptr || obj->is_immutable()) + return false; + + if (obj->is_local_object()) + { + std::cout << "Adding object to region: " << obj->get_name() + << " rc = " << obj->rc << std::endl; + rc_of_added_objects += obj->rc; + internal_references++; + obj->region = {r}; + get_local_region()->objects.erase(obj); + r->objects.insert(obj); + return true; + } + + auto obj_region = get_region(obj); + if (obj_region == r) + { + std::cout << "Adding internal reference to object: " + << obj->get_name() << std::endl; + internal_references++; + return false; + } + + Region::set_parent(obj_region, r); + Region::dec_lrc(obj_region); + return false; + }); + r->local_reference_count += rc_of_added_objects - internal_references; + + std::cout << "Added " << rc_of_added_objects - internal_references + << " to LRC of region" << std::endl; + std::cout << "Region LRC: " << r->local_reference_count << std::endl; + std::cout << "Internal references found: " << internal_references + << std::endl; + } + + public: + // prototype is borrowed, the caller does not need to provide an RC. + DynObject(DynObject* prototype_ = nullptr, bool global = false) + : prototype(prototype_) + { + if (!global) + { + count++; + all_objects.insert(this); + auto local_region = get_local_region(); + region = local_region; + local_region->objects.insert(this); + } + if (prototype != nullptr) + prototype->change_rc(1); + std::cout << "Allocate: " << get_name() << std::endl; + } + + // TODO This should use prototype lookup for the destructor. + virtual ~DynObject() + { + // Erase from set of all objects, and remove count if found. + auto matched = all_objects.erase(this); + count -= matched; + + // If it wasn't in the all_objects set, then it was a special object + // that we don't track for leaks, otherwise, we need to check if the + // RC is zero. + if (change_rc(0) != 0 && matched != 0) + { + ui::error("Object still has references"); + } + + auto r = get_region(this); + if (!is_immutable() && r != nullptr) + r->objects.erase(this); + + std::cout << "Deallocate: " << get_name() << std::endl; + } + + /// @brief The string representation of this value to + /// TODO remove virtual once we have primitive functions. + virtual std::string get_name() + { + std::stringstream stream; + stream << this; + return stream.str(); + } + + /// TODO remove virtual once we have primitive functions. + virtual DynObject* is_primitive() + { + return nullptr; + } + + void freeze() + { + // TODO SCC algorithm + visit(this, [](Edge e) { + auto obj = e.target; + if (obj->is_immutable()) + return false; + + auto r = get_region(obj); + if (r != nullptr) + { + get_region(obj)->objects.erase(obj); + } + obj->region.set_tag(ImmutableTag); + return true; + }); + } + + bool is_immutable() + { + return region.get_tag() == ImmutableTag; + } + + [[nodiscard]] DynObject* get(std::string name) + { + auto result = fields.find(name); + if (result != fields.end()) + return result->second; + + if (name == PrototypeField) + return prototype; + + // Search the prototype chain. + // TODO make this iterative. + if (prototype != nullptr) + return prototype->get(name); + + // No field or prototype chain found. + return nullptr; + } + + [[nodiscard]] DynObject* set(std::string name, DynObject* value) + { + if (is_immutable()) + { + ui::error("Cannot mutate immutable object"); + } + DynObject* old = fields[name]; + fields[name] = value; + return old; + } + + // The caller must provide an rc for value. + [[nodiscard]] DynObject* set_prototype(DynObject* value) + { + if (is_immutable()) + { + ui::error("Cannot mutate immutable object"); + } + DynObject* old = prototype; + prototype = value; + return old; + } + + DynObject* get_prototype() + { + return prototype; + } + + static void add_reference(DynObject* src, DynObject* target) + { + if (target == nullptr) + return; + + target->change_rc(1); + + auto src_region = get_region(src); + add_region_reference(src_region, target); + } + + static void + remove_reference(DynObject* src_initial, DynObject* old_dst_initial) + { + visit( + {src_initial, "", old_dst_initial}, + [&](Edge e) { + if (e.target == nullptr) + return false; + + std::cout << "Remove reference from: " << e.src->get_name() << " to " + << e.target->get_name() << std::endl; + bool result = e.target->change_rc(-1) == 0; + + remove_region_reference(get_region(e.src), get_region(e.target)); + return result; + }, + [&](DynObject* obj) { delete obj; }); + + Region::collect(); + } + + static void + move_reference(DynObject* src, DynObject* dst, DynObject* target) + { + if (target == nullptr || target->is_immutable()) + return; + + auto src_region = get_region(src); + auto dst_region = get_region(dst); + + if (src_region == dst_region) + return; + + auto target_region = get_region(target); + + add_region_reference(dst_region, target); + // Note that target_region and get_region(target) are not necessarily the + // same. + remove_region_reference(src_region, target_region); + } + + void create_region() + { + Region* r = new Region(); + add_to_region(r); + // Add root reference as external. + r->local_reference_count++; + } + + static size_t get_count() + { + return count; + } + + static Region* get_local_region() + { + return local_region; + } + + static std::set get_objects() + { + return all_objects; + } + }; + + inline void destruct(DynObject* obj) + { + // Called from the region destructor. + // Remove all references to other objects. + // If in the same region, then just remove the RC, but don't try to collect + // as the whole region is being torndown including any potential cycles. + auto same_region = [](DynObject* src, DynObject* target) { + return DynObject::get_region(src) == DynObject::get_region(target); + }; + for (auto& [key, field] : obj->fields) + { + if (field == nullptr) + continue; + if (same_region(obj, field)) + { + // Same region just remove the rc, but don't try to collect. + field->change_rc(-1); + continue; + } + + auto old_value = obj->set(key, nullptr); + remove_reference(obj, old_value); + } + + if (same_region(obj, obj->prototype)) + { + obj->prototype->change_rc(-1); + } + else + { + auto old_value = obj->set_prototype(nullptr); + remove_reference(obj, old_value); + } + } + + inline void dealloc(DynObject* obj) + { + // Called from the region destructor. + // So remove from region if in one. + // This ensures we don't try to remove it from the set that is being + // iterated. + obj->region = nullptr; + delete obj; + } + + template + inline void visit(Edge e, Pre pre, Post post) + { + if (!pre(e)) + return; + + constexpr bool HasPost = !std::is_same_v; + constexpr uintptr_t POST{1}; + + std::vector, std::string>> stack; + + auto visit_object = [&](DynObject* obj) { + if (obj == nullptr) + return; + if constexpr (HasPost) + stack.push_back({{obj, POST}, ""}); + // TODO This will need to depend on the type of object. + for (auto& [key, field] : obj->fields) + stack.push_back({obj, key}); + if (obj->prototype != nullptr) + stack.push_back({obj, PrototypeField}); + }; + + visit_object(e.target); + + while (!stack.empty()) + { + auto [obj, key] = stack.back(); + auto obj_ptr = obj.get_ptr(); + stack.pop_back(); + + if (HasPost && obj.get_tag() == POST) + { + post(obj_ptr); + continue; + } + + DynObject* next = obj_ptr->get(key); + if (pre({obj_ptr, key, next})) + { + visit_object(next); + } + } + } + + template + inline void visit(DynObject* start, Pre pre, Post post) + { + visit(Edge{nullptr, "", start}, pre, post); + } + +} // namespace rt::objects diff --git a/src/rt/objects/region.h b/src/rt/objects/region.h new file mode 100644 index 0000000..cbb82e2 --- /dev/null +++ b/src/rt/objects/region.h @@ -0,0 +1,210 @@ +#pragma once + +#include "../../utils/tagged_pointer.h" +#include "../ui.h" + +#include +#include + +namespace rt::objects +{ + class DynObject; + + void destruct(DynObject* obj); + void dealloc(DynObject* obj); + + // Represents the region of objects + struct Region + { + static inline thread_local std::vector to_collect{}; + // The local reference count is the number of references to objects in the + // region from local region. Using non-zero LRC for subregions ensures we + // cannot send a region if a subregion has references into it. Using zero + // and non-zero means we reduce the number of updates. + size_t local_reference_count{0}; + + // For nested regions, this points at the owning region. + // This guarantees that the regions for trees. + Region* parent{nullptr}; + + // The parent reference count is the number of references to the region from + // the parent region. In classic Verona we considered this as 0 or 1, but + // by tracking dynamically we can allow multiple references. + size_t parent_reference_count{0}; + + // The number of direct subregions, whose LRC is non-zero + size_t sub_region_reference_count{0}; + + // The objects in this region. + // TODO: make this more efficient. + std::set objects{}; + + size_t combined_lrc() + { + return local_reference_count + sub_region_reference_count; + } + + static void action(Region* r) + { + if ((r->local_reference_count == 0) && (r->parent == nullptr)) + { + // TODO, this can be hooked to perform delayed operations like send. + // Needs to check for sub_region_reference_count for send, but not + // deallocate. + + to_collect.push_back(r); + std::cout << "Collecting region: " << r << std::endl; + } + } + + static void dec_lrc(Region* r) + { + assert(r->local_reference_count != 0); + r->local_reference_count--; + // Edge triggered LRC for parent. + if (r->combined_lrc() == 0) + dec_sbrc(r); + else + action(r); + } + + static void dec_sbrc(Region* r) + { + while (r->parent != nullptr) + { + r = r->parent; + r->sub_region_reference_count--; + if (r->combined_lrc() != 0) + break; + } + action(r); + } + + static void inc_lrc(Region* r) + { + r->local_reference_count++; + // Edge triggered LRC for parent. + if (r->combined_lrc() == 1) + inc_sbrc(r); + } + + static void inc_sbrc(Region* r) + { + while (r->parent != nullptr) + { + r = r->parent; + r->sub_region_reference_count++; + if (r->combined_lrc() != 1) + break; + } + } + + static void dec_prc(Region* r) + { + std::cout << "Dropping parent reference: " << r << std::endl; + assert(r->parent_reference_count != 0); + r->parent_reference_count--; + if (r->parent_reference_count != 0) + return; + + // This is the last parent reference, so region no longer has a parent. + // If it has internal references, then we need to decrement the parents + // local reference count. + if (r->combined_lrc() != 0) + dec_sbrc(r); + else + { + std::cout << "Collecting region: " << r << std::endl; + to_collect.push_back(r); + } + // Unset parent pointer. + r->parent = nullptr; + } + + static void set_parent(Region* r, Region* p) + { + assert(r->local_reference_count != 0); + + r->parent_reference_count++; + + // Check if already parented, if so increment the parent reference count. + if (r->parent == p) + { + return; + } + + // Check if already parented to another region. + if (r->parent != nullptr) + ui::error( + "Region already has a parent: Creating region DAG not supported!"); + + // Prevent creating a cycle + auto ancestors = p->parent; + while (ancestors != nullptr) + { + if (ancestors == r) + ui::error("Cycle created in region hierarchy"); + ancestors = ancestors->parent; + } + + // Set the parent and increment the parent reference count. + r->parent = p; + assert(r->parent_reference_count == 1); + + // If the region has local references, then we need the parent to have a + // local reference to. + if (r->local_reference_count == 0) + return; + + inc_sbrc(r); + } + + void terminate_region() + { + to_collect.push_back(this); + collect(); + } + + std::vector get_objects() + { + std::vector result; + for (auto o : objects) + result.push_back(o); + return result; + } + + static void collect() + { + // Reentrancy guard. + static thread_local bool collecting = false; + if (collecting) + return; + + collecting = true; + + std::cout << "Starting collection" << std::endl; + while (!to_collect.empty()) + { + auto r = to_collect.back(); + to_collect.pop_back(); + // Note destruct could re-enter here, ensure we don't hold onto a + // pointer into to_collect. + for (auto o : r->objects) + destruct(o); + for (auto o : r->objects) + dealloc(o); + r->objects.clear(); + + delete r; + } + std::cout << "Finished collection" << std::endl; + + collecting = false; + } + }; + + // Represents the region of specific object. Uses small pointers to + // encode special regions. + using RegionPointer = utils::TaggedPointer; + +} // namespace rt::objects diff --git a/src/rt/objects/visit.h b/src/rt/objects/visit.h new file mode 100644 index 0000000..3a851f1 --- /dev/null +++ b/src/rt/objects/visit.h @@ -0,0 +1,25 @@ +#pragma once + +#include "../../utils/nop.h" + +#include + +namespace rt::objects +{ + class DynObject; + + struct Edge + { + DynObject* src; + std::string key; + DynObject* target; + }; + + using NopDO = utils::Nop; + + template + inline void visit(Edge e, Pre pre, Post post = {}); + + template + inline void visit(DynObject* start, Pre pre, Post post = {}); +} diff --git a/src/rt/output.h b/src/rt/output.h deleted file mode 100644 index ee861a3..0000000 --- a/src/rt/output.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include -#include - -namespace objects { - [[noreturn]] inline void error(const std::string &msg) { - std::cerr << "Error: " << msg << std::endl; - std::exit(1); - } -} \ No newline at end of file diff --git a/src/rt/region.h b/src/rt/region.h deleted file mode 100644 index 843133a..0000000 --- a/src/rt/region.h +++ /dev/null @@ -1,188 +0,0 @@ -#pragma once - -#include -#include - -#include "output.h" - -namespace objects { -class DynObject; - -void destruct(DynObject *obj); -void dealloc(DynObject *obj); - -// Represents the region of objects -struct Region { - - static inline thread_local std::vector to_collect{}; - // The local reference count is the number of references to objects in the region from local region. - // Using non-zero LRC for subregions ensures we cannot send a region if a - // subregion has references into it. Using zero and non-zero means we reduce - // the number of updates. - size_t local_reference_count{0}; - - // For nested regions, this points at the owning region. - // This guarantees that the regions for trees. - Region *parent{nullptr}; - - // The parent reference count is the number of references to the region from - // the parent region. In classic Verona we considered this as 0 or 1, but by - // tracking dynamically we can allow multiple references. - size_t parent_reference_count{0}; - - // The number of direct subregions, whose LRC is non-zero - size_t sub_region_reference_count{0}; - - // The objects in this region. - // TODO: make this more efficient. - std::set objects{}; - - size_t combined_lrc() - { - return local_reference_count + sub_region_reference_count; - } - - static void action(Region *r) { - if ((r->local_reference_count == 0) && (r->parent == nullptr)) { - // TODO, this can be hooked to perform delayed operations like send. - // Needs to check for sub_region_reference_count for send, but not deallocate. - - to_collect.push_back(r); - std::cout << "Collecting region: " << r << std::endl; - } - } - - static void dec_lrc(Region *r) { - assert(r->local_reference_count != 0); - r->local_reference_count--; - // Edge triggered LRC for parent. - if(r->combined_lrc() == 0) - dec_sbrc(r); - else - action(r); - } - - static void dec_sbrc(Region *r) - { - while (r->parent != nullptr) - { - r = r->parent; - r->sub_region_reference_count--; - if (r->combined_lrc() != 0) - break; - } - action(r); - } - - static void inc_lrc(Region *r) { - r->local_reference_count++; - // Edge triggered LRC for parent. - if (r->combined_lrc() == 1) - inc_sbrc(r); - } - - static void inc_sbrc(Region *r) - { - while (r->parent != nullptr) - { - r = r->parent; - r->sub_region_reference_count++; - if (r->combined_lrc() != 1) - break; - } - } - - static void dec_prc(Region *r) { - std::cout << "Dropping parent reference: " << r << std::endl; - assert(r->parent_reference_count != 0); - r->parent_reference_count--; - if (r->parent_reference_count != 0) - return; - - // This is the last parent reference, so region no longer has a parent. - // If it has internal references, then we need to decrement the parents - // local reference count. - if (r->combined_lrc() != 0) - dec_sbrc(r); - else { - std::cout << "Collecting region: " << r << std::endl; - to_collect.push_back(r); - } - // Unset parent pointer. - r->parent = nullptr; - } - - static void set_parent(Region *r, Region *p) { - assert(r->local_reference_count != 0); - - r->parent_reference_count++; - - // Check if already parented, if so increment the parent reference count. - if (r->parent == p) { - return; - } - - // Check if already parented to another region. - if (r->parent != nullptr) - error("Region already has a parent: Creating region DAG not supported!"); - - // Prevent creating a cycle - auto ancestors = p->parent; - while (ancestors != nullptr) { - if (ancestors == r) - error("Cycle created in region hierarchy"); - ancestors = ancestors->parent; - } - - // Set the parent and increment the parent reference count. - r->parent = p; - assert(r->parent_reference_count == 1); - - // If the region has local references, then we need the parent to have a - // local reference to. - if (r->local_reference_count == 0) - return; - - inc_sbrc(r); - } - - void terminate_region() { - to_collect.push_back(this); - collect(); - } - - std::vector get_objects() { - std::vector result; - for (auto o : objects) - result.push_back(o); - return result; - } - - static void collect() { - // Reentrancy guard. - static thread_local bool collecting = false; - if (collecting) - return; - - collecting = true; - - std::cout << "Starting collection" << std::endl; - while (!to_collect.empty()) { - auto r = to_collect.back(); - to_collect.pop_back(); - // Note destruct could re-enter here, ensure we don't hold onto a pointer - // into to_collect. - for (auto o : r->objects) - destruct(o); - for (auto o : r->objects) - dealloc(o); - r->objects.clear(); - - delete r; - } - std::cout << "Finished collection" << std::endl; - - collecting = false; - } -}; -} // namespace objects \ No newline at end of file diff --git a/src/rt/rt.cc b/src/rt/rt.cc index 6c48bf5..439e87a 100644 --- a/src/rt/rt.cc +++ b/src/rt/rt.cc @@ -1,142 +1,192 @@ +#include "rt.h" + +#include "core.h" +#include "objects/dyn_object.h" + #include #include #include -#include "mermaid.h" -#include "objects.h" -#include "rt.h" +namespace rt +{ + + objects::DynObject* make_func(verona::interpreter::Bytecode* body) + { + return new core::BytecodeFuncObject(body); + } + objects::DynObject* make_iter(objects::DynObject* iter_src) + { + return new core::KeyIterObject(iter_src->fields); + } + objects::DynObject* make_str(std::string value) + { + return new core::StringObject(value); + } + objects::DynObject* make_object() + { + return new objects::DynObject(); + } + + objects::DynObject* make_frame(objects::DynObject* parent) + { + return new core::FrameObject(parent); + } -namespace objects { - -DynObject *make_func(verona::interpreter::Bytecode *body) { - return new BytecodeFuncObject(body); -} -DynObject *make_iter(DynObject *iter_src) { - return new KeyIterObject(iter_src->fields); -} -DynObject *make_object(std::string value) { - return new StringObject(value); -} -DynObject *make_object() { return new DynObject(); } - -thread_local std::vector DynObject::frame_stack = { FrameObject::create_first_stack() }; - -void push_frame() { - auto parent = DynObject::frame(); - DynObject::push_frame(new FrameObject(parent)); -} -DynObject *get_frame() { return DynObject::frame(); } -void pop_frame() { - DynObject::pop_frame(); -} - -void freeze(DynObject *obj) { obj->freeze(); } - -void create_region(DynObject *object) { object->create_region(); } - -DynObject *get(DynObject *obj, std::string key) { - return obj->get(key); -} - -std::string get_key(DynObject* key) { - // TODO Add some checking. This is need to lookup the correct function in the prototype chain. - if (key->get_prototype() != &stringPrototypeObject) { - error("Key must be a string."); - } - StringObject *str_key = reinterpret_cast(key); - return str_key->as_key(); -} - -DynObject *get(DynObject *obj, DynObject *key) { - return get(obj, get_key(key)); -} - -DynObject *set(DynObject *obj, std::string key, DynObject *value) { - return obj->set(key, value); -} - -DynObject *set(DynObject *obj, DynObject *key, DynObject *value) { - return set(obj, get_key(key), value); -} - -// TODO [[nodiscard]] -DynObject *set_prototype(DynObject *obj, DynObject *proto) { - if (proto->is_primitive() != nullptr) { - error("Cannot set a primitive as a prototype."); - } - if (obj->is_primitive() != nullptr) { - error("Cannot set a prototype on a primitive object."); - } - return obj->set_prototype(proto); -} - -void add_reference(DynObject *src, DynObject *target) { - DynObject::add_reference(src, target); -} - -void remove_reference(DynObject *src, DynObject *target) { - DynObject::remove_reference(src, target); -} - -void move_reference(DynObject *src, DynObject *dst, DynObject *target) { - DynObject::move_reference(src, dst, target); -} - -size_t pre_run() { - objects::DynObject::set_local_region(new Region()); - std::cout << "Running test..." << std::endl; - return objects::DynObject::get_count(); -} - -void post_run(size_t initial_count, UI& ui) { - std::cout << "Test complete - checking for cycles in local region..." - << std::endl; - if (objects::DynObject::get_count() != initial_count) { - std::cout << "Cycles detected in local region." << std::endl; - auto objs = objects::DynObject::get_local_region()->get_objects(); - std::vector edges; - for (auto obj : objs) { - edges.push_back({nullptr, "?", obj}); + thread_local objects::RegionPointer objects::DynObject::local_region = + new Region(); + + void freeze(objects::DynObject* obj) + { + obj->freeze(); + } + + void create_region(objects::DynObject* object) + { + object->create_region(); + } + + objects::DynObject* get(objects::DynObject* obj, std::string key) + { + return obj->get(key); + } + + std::string get_key(objects::DynObject* key) + { + // TODO Add some checking. This is need to lookup the correct function in + // the prototype chain. + if (key->get_prototype() != &core::stringPrototypeObject) + { + ui::error("Key must be a string."); } - ui.output(edges, "Cycles detected in local region."); + core::StringObject* str_key = reinterpret_cast(key); + return str_key->as_key(); } - objects::DynObject::get_local_region()->terminate_region(); - if (objects::DynObject::get_count() != initial_count) { - std::cout << "Memory leak detected!" << std::endl; - std::cout << "Initial count: " << initial_count << std::endl; - std::cout << "Final count: " << objects::DynObject::get_count() - << std::endl; - std::vector edges; - for (auto obj : objects::DynObject::get_objects()) { - edges.push_back({nullptr, "?", obj}); + objects::DynObject* get(objects::DynObject* obj, objects::DynObject* key) + { + return get(obj, get_key(key)); + } + + objects::DynObject* + set(objects::DynObject* obj, std::string key, objects::DynObject* value) + { + return obj->set(key, value); + } + + objects::DynObject* set( + objects::DynObject* obj, objects::DynObject* key, objects::DynObject* value) + { + return set(obj, get_key(key), value); + } + + // TODO [[nodiscard]] + objects::DynObject* + set_prototype(objects::DynObject* obj, objects::DynObject* proto) + { + if (proto->is_primitive() != nullptr) + { + ui::error("Cannot set a primitive as a prototype."); } - ui.output(edges, "Memory leak detected!"); + if (obj->is_primitive() != nullptr) + { + ui::error("Cannot set a prototype on a primitive object."); + } + return obj->set_prototype(proto); + } - std::exit(1); - } else { - std::cout << "No memory leaks detected!" << std::endl; + objects::DynObject* get_true() + { + return &core::TrueObject; + } + objects::DynObject* get_false() + { + return &core::FalseObject; } -} -namespace value { - DynObject *iter_next(DynObject *iter) { + void add_reference(objects::DynObject* src, objects::DynObject* target) + { + objects::DynObject::add_reference(src, target); + } + + void remove_reference(objects::DynObject* src, objects::DynObject* target) + { + objects::DynObject::remove_reference(src, target); + } + + void move_reference( + objects::DynObject* src, + objects::DynObject* dst, + objects::DynObject* target) + { + objects::DynObject::move_reference(src, dst, target); + } + + size_t pre_run() + { + std::cout << "Running test..." << std::endl; + return objects::DynObject::get_count(); + } + + void post_run(size_t initial_count, ui::UI& ui) + { + std::cout << "Test complete - checking for cycles in local region..." + << std::endl; + if (objects::DynObject::get_count() != initial_count) + { + std::cout << "Cycles detected in local region." << std::endl; + auto objs = objects::DynObject::get_local_region()->get_objects(); + std::vector edges; + for (auto obj : objs) + { + edges.push_back({nullptr, "?", obj}); + } + ui.output(edges, "Cycles detected in local region."); + } + objects::DynObject::get_local_region()->terminate_region(); + if (objects::DynObject::get_count() != initial_count) + { + std::cout << "Memory leak detected!" << std::endl; + std::cout << "Initial count: " << initial_count << std::endl; + std::cout << "Final count: " << objects::DynObject::get_count() + << std::endl; + + std::vector edges; + for (auto obj : objects::DynObject::get_objects()) + { + edges.push_back({nullptr, "?", obj}); + } + ui.output(edges, "Memory leak detected!"); + + std::exit(1); + } + else + { + std::cout << "No memory leaks detected!" << std::endl; + } + } + + objects::DynObject* iter_next(objects::DynObject* iter) + { assert(!iter->is_immutable()); - if (iter->get_prototype() != &objects::keyIterPrototypeObject) { - error("Object is not an iterator."); + if (iter->get_prototype() != &core::keyIterPrototypeObject) + { + ui::error("Object is not an iterator."); } - return reinterpret_cast(iter)->iter_next(); + return reinterpret_cast(iter)->iter_next(); } - verona::interpreter::Bytecode* get_bytecode(objects::DynObject *func) { - if (func->get_prototype() == &objects::bytecodeFuncPrototypeObject) { - return reinterpret_cast(func)->get_bytecode(); - } else { - error("Object is not a function."); + verona::interpreter::Bytecode* get_bytecode(objects::DynObject* func) + { + if (func->get_prototype() == &core::bytecodeFuncPrototypeObject) + { + return reinterpret_cast(func)->get_bytecode(); + } + else + { + ui::error("Object is not a function."); return {}; } } -} -} // namespace objects +} // namespace rt diff --git a/src/rt/rt.h b/src/rt/rt.h index 7f02667..4fc2046 100644 --- a/src/rt/rt.h +++ b/src/rt/rt.h @@ -1,62 +1,50 @@ #pragma once -#include -#include -#include -#include - #include "../lang/interpreter.h" +#include "objects/visit.h" +#include "ui.h" -namespace objects { -class DynObject; - -struct Edge { - DynObject *src; - std::string key; - DynObject *target; -}; - -DynObject *make_func(verona::interpreter::Bytecode *body); -DynObject *make_iter(DynObject *iter_src); -DynObject *make_object(std::string str_value); -DynObject *make_object(); - -/// @brief This pushes a new frame onto the frame stack -void push_frame(); -/// @brief Returns the current frame at the top of the frame stack. -DynObject *get_frame(); -/// @brief This pops the current frame. -void pop_frame(); +#include +#include -void freeze(DynObject *obj); -void create_region(DynObject *objects); +namespace rt +{ -DynObject *get(DynObject *src, std::string key); -DynObject *get(DynObject *src, DynObject *key); -DynObject *set(DynObject *dst, std::string key, DynObject *value); -DynObject *set(DynObject *dst, DynObject *key, DynObject *value); + objects::DynObject* make_func(verona::interpreter::Bytecode* body); + objects::DynObject* make_iter(objects::DynObject* iter_src); + objects::DynObject* make_str(std::string str_value); + objects::DynObject* make_object(); + objects::DynObject* make_frame(objects::DynObject* parent); -DynObject *set_prototype(DynObject *obj, DynObject *proto); + void freeze(objects::DynObject* obj); + void create_region(objects::DynObject* objects); -void add_reference(DynObject *src, DynObject *target); -void remove_reference(DynObject *src, DynObject *target); -void move_reference(DynObject *src, DynObject *dst, DynObject *target); + objects::DynObject* get(objects::DynObject* src, std::string key); + objects::DynObject* get(objects::DynObject* src, objects::DynObject* key); + objects::DynObject* + set(objects::DynObject* dst, std::string key, objects::DynObject* value); + objects::DynObject* set( + objects::DynObject* dst, + objects::DynObject* key, + objects::DynObject* value); -size_t get_object_count(); + objects::DynObject* + set_prototype(objects::DynObject* obj, objects::DynObject* proto); -struct UI -{ - virtual void output(std::vector &, std::string ) {} -}; + objects::DynObject* get_true(); + objects::DynObject* get_false(); -size_t pre_run(); -void post_run(size_t count, UI& ui); + void add_reference(objects::DynObject* src, objects::DynObject* target); + void remove_reference(objects::DynObject* src, objects::DynObject* target); + void move_reference( + objects::DynObject* src, + objects::DynObject* dst, + objects::DynObject* target); -void mermaid(std::vector &roots, std::ostream &out); + size_t pre_run(); + void post_run(size_t count, rt::ui::UI& ui); -namespace value { - DynObject *iter_next(DynObject *iter); - verona::interpreter::Bytecode* get_bytecode(objects::DynObject *func); -} + objects::DynObject* iter_next(objects::DynObject* iter); + verona::interpreter::Bytecode* get_bytecode(objects::DynObject* func); -} // namespace objects +} // namespace rt diff --git a/src/rt/ui.h b/src/rt/ui.h new file mode 100644 index 0000000..cb81967 --- /dev/null +++ b/src/rt/ui.h @@ -0,0 +1,25 @@ +#pragma once + +#include "objects/visit.h" + +#include +#include +#include +#include + +namespace rt::ui +{ + class UI + { + public: + virtual void output(std::vector&, std::string) {} + }; + + void mermaid(std::vector& roots, std::ostream& out); + + [[noreturn]] inline void error(const std::string& msg) + { + std::cerr << "Error: " << msg << std::endl; + std::exit(1); + } +} // namespace rt::ui diff --git a/src/rt/ui/mermaid.cc b/src/rt/ui/mermaid.cc new file mode 100644 index 0000000..809b20c --- /dev/null +++ b/src/rt/ui/mermaid.cc @@ -0,0 +1,151 @@ +#include "../objects/dyn_object.h" +#include "../ui.h" + +#include +#include +#include +#include + +namespace rt::ui +{ + + void replace(std::string& text, std::string from, std::string replace) + { + size_t pos = 0; + while ((pos = text.find(from, pos)) != std::string::npos) + { + text.replace(pos, from.length(), replace); + pos += replace.length(); // Move past the last replaced position + } + } + + std::string escape(std::string text) + { + replace(text, "[", "#91;"); + replace(text, "]", "#93;"); + replace(text, "_", "#95;"); + replace(text, "<", "#60;"); + replace(text, ">", "#62;"); + replace(text, "\"", "#34;"); + return text; + } + + void mermaid(std::vector& roots, std::ostream& out) + { + // Give a nice id to each object. + std::map visited; + // Keep track of all the objects in a region. + std::map> region_strings; + // Keep track of the immutable objects. + std::vector immutable_objects; + // // Add frame as local region + // visited[objects::DynObject::frame()] = 0; + // region_strings[objects::DynObject::get_local_region()] = {0}; + // Add nullptr as immutable + visited[nullptr] = 0; + immutable_objects.push_back(0); + // Account for frame and nullptr objects. + size_t id = 1; // was 2 + // Header + out << "```mermaid" << std::endl; + out << "graph TD" << std::endl; + + bool unreachable = false; + + auto explore = [&](objects::Edge e) { + objects::DynObject* dst = e.target; + std::string key = e.key; + objects::DynObject* src = e.src; + if (src != nullptr) + { + out << " id" << visited[src] << " -->|" << escape(key) << "| "; + } + if (visited.find(dst) != visited.end()) + { + out << "id" << visited[dst] << std::endl; + return false; + } + auto curr_id = id++; + visited[dst] = curr_id; + out << "id" << curr_id << "[ "; + out << escape(dst->get_name()); + out << "
rc=" << dst->rc; + + out << " ]" << (unreachable ? ":::unreachable" : "") << std::endl; + + auto region = objects::DynObject::get_region(dst); + if (region != nullptr) + { + region_strings[region].push_back(curr_id); + } + + if (dst->is_immutable()) + { + immutable_objects.push_back(curr_id); + } + return true; + }; + // Output all reachable edges + for (auto& root : roots) + { + objects::visit({root.src, root.key, root.target}, explore); + } + + // Output the unreachable parts of the graph + unreachable = true; + for (auto& root : objects::DynObject::all_objects) + { + objects::visit({nullptr, "", root}, explore); + } + + // Output any region parent edges. + for (auto [region, objects] : region_strings) + { + if (region->parent == nullptr) + continue; + out << " region" << region->parent << " <-.-o region" << region + << std::endl; + } + + // Output all the region membership information + for (auto [region, objects] : region_strings) + { + out << "subgraph "; + + if (region == objects::DynObject::get_local_region()) + { + out << "local region" << std::endl; + } + else + { + out << std::endl; + out << " region" << region << "[\\" << region + << "
lrc=" << region->local_reference_count + << "
sbrc=" << region->sub_region_reference_count + << "
prc=" << region->parent_reference_count << "/]" + << std::endl; + } + for (auto obj : objects) + { + out << " id" << obj << std::endl; + } + out << "end" << std::endl; + } + + // Output the immutable region. + out << "subgraph Immutable" << std::endl; + out << " id0[nullptr]" << std::endl; + for (auto obj : immutable_objects) + { + out << " id" << obj << std::endl; + } + out << "end" << std::endl; + + // Output object count as very useful. + out << "subgraph Count " << objects::DynObject::get_count() << std::endl; + out << "end" << std::endl; + out << "classDef unreachable stroke:red,stroke-width:2px" << std::endl; + // Footer (end of mermaid graph) + out << "```" << std::endl; + } +} // namespace rt::ui diff --git a/src/utils/nop.h b/src/utils/nop.h new file mode 100644 index 0000000..07580eb --- /dev/null +++ b/src/utils/nop.h @@ -0,0 +1,11 @@ +#pragma once + +namespace utils +{ + template + class Nop + { + public: + void operator()(T) const {} + }; +} diff --git a/src/rt/tagged_pointer.h b/src/utils/tagged_pointer.h similarity index 62% rename from src/rt/tagged_pointer.h rename to src/utils/tagged_pointer.h index 02276de..24a0db9 100644 --- a/src/rt/tagged_pointer.h +++ b/src/utils/tagged_pointer.h @@ -6,7 +6,7 @@ namespace utils { - template + template class TaggedPointer { uintptr_t ptr; @@ -17,13 +17,25 @@ namespace utils TaggedPointer(T* ptr) : ptr(reinterpret_cast(ptr)) {} constexpr TaggedPointer(std::nullptr_t) : ptr(0) {} - TaggedPointer(T* ptr, uintptr_t tag) : ptr(reinterpret_cast(ptr) | tag) - { assert(tag < 4); } + TaggedPointer(T* ptr, uintptr_t tag) + : ptr(reinterpret_cast(ptr) | tag) + { + assert(tag < 4); + } - constexpr TaggedPointer(std::nullptr_t, std::uintptr_t tag) : ptr(tag) { assert(tag < 4); } + constexpr TaggedPointer(std::nullptr_t, std::uintptr_t tag) : ptr(tag) + { + assert(tag < 4); + } - bool operator==(TaggedPointer other) const { return ptr == other.ptr; } - bool operator!=(TaggedPointer other) const { return ptr != other.ptr; } + bool operator==(TaggedPointer other) const + { + return ptr == other.ptr; + } + bool operator!=(TaggedPointer other) const + { + return ptr != other.ptr; + } void set_tag(uintptr_t tag) { @@ -48,13 +60,19 @@ namespace utils return reinterpret_cast(ptr & ~0x3); } - operator T*() const { return get_ptr(); } + operator T*() const + { + return get_ptr(); + } - T& operator *() const { return *get_ptr(); } + T& operator*() const + { + return *get_ptr(); + } uintptr_t get_tag() const { return ptr & 0x3; } }; -} \ No newline at end of file +}