[libjlx] First successful "execution"
This commit is contained in:
parent
26e1f3a173
commit
7c1612d9c2
6 changed files with 881 additions and 21 deletions
|
|
@ -12,7 +12,6 @@ int main(int argc, char** argv) {
|
||||||
std::istreambuf_iterator<char> start, end;
|
std::istreambuf_iterator<char> start, end;
|
||||||
if (argc > 1) {
|
if (argc > 1) {
|
||||||
if (std::filesystem::is_regular_file(argv[1])) {
|
if (std::filesystem::is_regular_file(argv[1])) {
|
||||||
std::cout << "Opening: " << argv[1] << std::endl;
|
|
||||||
file = std::ifstream(argv[1]);
|
file = std::ifstream(argv[1]);
|
||||||
start = std::istreambuf_iterator<char>(file);
|
start = std::istreambuf_iterator<char>(file);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -37,8 +36,6 @@ int main(int argc, char** argv) {
|
||||||
}
|
}
|
||||||
} while (res.has_value());
|
} while (res.has_value());
|
||||||
|
|
||||||
std::cout << "Read " << tokens.size() << " tokens\n";
|
|
||||||
|
|
||||||
auto last = std::string();
|
auto last = std::string();
|
||||||
while(res.has_value()) {
|
while(res.has_value()) {
|
||||||
const auto& t = res.value();
|
const auto& t = res.value();
|
||||||
|
|
@ -61,8 +58,6 @@ int main(int argc, char** argv) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "Parsed " << rt->statements.size() << " statements" << std::endl;
|
|
||||||
|
|
||||||
auto type_checker = jlx::type_checker(rt->statements.begin(), rt->statements.end());
|
auto type_checker = jlx::type_checker(rt->statements.begin(), rt->statements.end());
|
||||||
type_checker.include_stdlib();
|
type_checker.include_stdlib();
|
||||||
try {
|
try {
|
||||||
|
|
@ -72,7 +67,10 @@ int main(int argc, char** argv) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "Type checking OK." << std::endl;
|
auto interpreter = jlx::interpreter(rt->statements.begin(), rt->statements.end());
|
||||||
|
interpreter.add_stdlib();
|
||||||
|
|
||||||
|
interpreter.run();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ target_sources(libjlx PUBLIC FILE_SET libjlx_modules TYPE CXX_MODULES FILES
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/modules/ast.cppm"
|
"${CMAKE_CURRENT_SOURCE_DIR}/modules/ast.cppm"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/modules/utils.cppm"
|
"${CMAKE_CURRENT_SOURCE_DIR}/modules/utils.cppm"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/modules/type_checker.cppm"
|
"${CMAKE_CURRENT_SOURCE_DIR}/modules/type_checker.cppm"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/modules/interpreter.cppm"
|
||||||
)
|
)
|
||||||
target_compile_options(libjlx PRIVATE $<IF:$<CXX_COMPILER_ID:Msvc>,/W4 /WX,-Wall -Wextra -Werror>)
|
target_compile_options(libjlx PRIVATE $<IF:$<CXX_COMPILER_ID:Msvc>,/W4 /WX,-Wall -Wextra -Werror>)
|
||||||
target_include_directories(libjlx PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
target_include_directories(libjlx PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||||
|
|
|
||||||
|
|
@ -409,7 +409,6 @@ namespace jlx {
|
||||||
|
|
||||||
auto start = *current;
|
auto start = *current;
|
||||||
|
|
||||||
|
|
||||||
switch(current->type) {
|
switch(current->type) {
|
||||||
case Identifier:
|
case Identifier:
|
||||||
if (previous != nullptr) {
|
if (previous != nullptr) {
|
||||||
|
|
@ -512,6 +511,8 @@ namespace jlx {
|
||||||
fail_invalid_token(*current);
|
fail_invalid_token(*current);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fail_invalid_token(*current);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<function_declaration> parse_function() {
|
std::unique_ptr<function_declaration> parse_function() {
|
||||||
|
|
|
||||||
859
libjlx/modules/interpreter.cppm
Normal file
859
libjlx/modules/interpreter.cppm
Normal file
|
|
@ -0,0 +1,859 @@
|
||||||
|
module;
|
||||||
|
|
||||||
|
#include <charconv>
|
||||||
|
#include <iterator>
|
||||||
|
#include <variant>
|
||||||
|
#include <optional>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <string_view>
|
||||||
|
#include <ranges>
|
||||||
|
#include <format>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
export module jlx:interpreter;
|
||||||
|
import :ast;
|
||||||
|
import :type_checker;
|
||||||
|
|
||||||
|
namespace jlx {
|
||||||
|
using runtime_value = std::variant<std::string, int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t, uint32_t, uint64_t, float, double, bool>;
|
||||||
|
|
||||||
|
struct interpreter_variable {
|
||||||
|
std::string type;
|
||||||
|
bool writable;
|
||||||
|
runtime_value value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct interpreter_variable_scope {
|
||||||
|
std::unordered_map<std::string, interpreter_variable> variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct interpreter_function_scope {
|
||||||
|
std::optional<runtime_value> return_value;
|
||||||
|
bool returned = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct interpreter_error : public std::runtime_error {
|
||||||
|
interpreter_error(std::string_view msg, const token& t) :
|
||||||
|
std::runtime_error(std::format("Interpreter error: {} at {}:{}:{}", msg, t.source_file, t.line, t.col).c_str()) {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class... Ts>
|
||||||
|
struct overload : Ts... { using Ts::operator()...; };
|
||||||
|
|
||||||
|
struct base_interpreter {
|
||||||
|
virtual std::optional<runtime_value> execute_statement(const statement&) = 0;
|
||||||
|
virtual void push_function_scope() = 0;
|
||||||
|
virtual void pop_function_scope() = 0;
|
||||||
|
virtual void push_variable_scope() = 0;
|
||||||
|
virtual void pop_variable_scope() = 0;
|
||||||
|
virtual interpreter_function_scope& current_function_scope() = 0;
|
||||||
|
virtual interpreter_variable_scope& current_variable_scope() = 0;
|
||||||
|
virtual bool is_in_function() = 0;
|
||||||
|
virtual void inject_value(const std::string_view&, runtime_value) = 0;
|
||||||
|
|
||||||
|
virtual ~base_interpreter() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct interpreter_function {
|
||||||
|
virtual std::optional<runtime_value> call(const std::vector<runtime_value>&, base_interpreter&) = 0;
|
||||||
|
|
||||||
|
virtual ~interpreter_function() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename A, typename B>
|
||||||
|
concept Operable = requires(A a, B b)
|
||||||
|
{
|
||||||
|
requires sizeof(A) >= sizeof(B);
|
||||||
|
{ a + b };
|
||||||
|
{ a - b };
|
||||||
|
{ a * b };
|
||||||
|
{ a / b };
|
||||||
|
{ a % b };
|
||||||
|
std::numeric_limits<A>::is_specialized;
|
||||||
|
std::numeric_limits<B>::is_specialized;
|
||||||
|
std::numeric_limits<A>::is_integer == std::numeric_limits<B>::is_integer;
|
||||||
|
std::numeric_limits<A>::is_signed == std::numeric_limits<B>::is_signed;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename A, typename B>
|
||||||
|
concept Comparable = requires {
|
||||||
|
requires std::is_same_v<A, B> ||
|
||||||
|
requires (A a, B b)
|
||||||
|
{
|
||||||
|
requires std::numeric_limits<A>::is_signed == std::numeric_limits<B>::is_signed;
|
||||||
|
{ a == b } -> std::convertible_to<bool>;
|
||||||
|
{ a != b } -> std::convertible_to<bool>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename A, typename B>
|
||||||
|
concept Orderable = requires
|
||||||
|
{
|
||||||
|
requires std::is_same_v<A, B> || requires (A a, B b){
|
||||||
|
requires std::numeric_limits<std::remove_cvref_t<A>>::is_signed == std::numeric_limits<std::remove_cvref_t<B>>::is_signed;
|
||||||
|
{ a < b } -> std::convertible_to<bool>;
|
||||||
|
{ a > b } -> std::convertible_to<bool>;
|
||||||
|
{ a >= b } -> std::convertible_to<bool>;
|
||||||
|
{ a <= b } -> std::convertible_to<bool>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(!Operable<std::string, uint32_t>);
|
||||||
|
static_assert(Operable<uint32_t, uint16_t>);
|
||||||
|
static_assert(!Operable<uint16_t, uint32_t>);
|
||||||
|
static_assert(!Comparable<std::string, signed char>);
|
||||||
|
static_assert(!Comparable<uint32_t, int32_t>);
|
||||||
|
static_assert(!Orderable<std::string, int32_t>);
|
||||||
|
static_assert(!Orderable<int8_t, uint32_t>);
|
||||||
|
|
||||||
|
template<typename A, typename B>
|
||||||
|
struct arithmetic_evaluator {
|
||||||
|
std::remove_cvref_t<A> sum(A a, B b) requires Operable<A, B> {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::remove_cvref_t<A> mul(A a, B b) requires Operable<A, B> {
|
||||||
|
return a * b;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::remove_cvref_t<A> div(A a, B b) requires Operable<A, B> {
|
||||||
|
return a / b;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::remove_cvref_t<A> sub(A a, B b) requires Operable<A, B> {
|
||||||
|
return a - b;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::remove_cvref_t<A> mod(A a, B b) requires Operable<A, B> {
|
||||||
|
return a % b;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::remove_cvref_t<B> sum(A a, B b) requires Operable<B, A> && (!Operable<A, B>) {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::remove_cvref_t<B> mul(A a, B b) requires Operable<B, A> && (!Operable<A, B>) {
|
||||||
|
return a * b;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::remove_cvref_t<B> div(A a, B b) requires Operable<B, A> && (!Operable<A, B>) {
|
||||||
|
return a / b;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::remove_cvref_t<B> sub(A a, B b) requires Operable<B, A> && (!Operable<A, B>) {
|
||||||
|
return a - b;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::remove_cvref_t<B> mod(A a, B b) requires Operable<B, A> && (!Operable<A, B>) {
|
||||||
|
return a % b;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string sum(std::string a, B b) requires (!std::is_same_v<std::remove_cvref_t<B>, std::string>) {
|
||||||
|
return a + std::to_string(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string sum(std::string a, std::string b) {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] A sum(A, B) requires (!std::is_same_v<std::remove_cvref_t<A>, std::string> && !Operable<A, B> && !Operable<B, A>) {
|
||||||
|
throw std::runtime_error("Arithmetically-incompatible types");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] A mul(A, B) {
|
||||||
|
throw std::runtime_error("Arithmetically-incompatible types");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] A div(A, B) {
|
||||||
|
throw std::runtime_error("Arithmetically-incompatible types");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] A sub(A, B) {
|
||||||
|
throw std::runtime_error("Arithmetically-incompatible types");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] A mod(A, B) {
|
||||||
|
throw std::runtime_error("Arithmetically-incompatible types");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<typename A, typename B> requires Comparable<A, B>
|
||||||
|
bool are_equal(A a, B b) {
|
||||||
|
return a == b;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename A, typename B>
|
||||||
|
[[noreturn]] bool are_equal(A, B) {
|
||||||
|
throw std::runtime_error("Non-comparable types");
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename A, typename B>
|
||||||
|
struct comparator {
|
||||||
|
bool more_than(A a, B b) requires Orderable<A, B> {
|
||||||
|
return a > b;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool less_than(A a, B b) requires Orderable<A, B> {
|
||||||
|
return a < b;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool more_equal_than(A a, B b) requires Orderable<A, B> {
|
||||||
|
return a >= b;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool less_equal_than(A a, B b) requires Orderable<A, B> {
|
||||||
|
return a <= b;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool more_than(A, B) {
|
||||||
|
throw std::runtime_error("Non-orderable types");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool less_than(A, B) {
|
||||||
|
throw std::runtime_error("Non-orderable types");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool more_equal_than(A, B) {
|
||||||
|
throw std::runtime_error("Non-orderable types");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool less_equal_than(A, B) {
|
||||||
|
throw std::runtime_error("Non-orderable types");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct lambda_function : public interpreter_function {
|
||||||
|
std::function<T(const std::vector<runtime_value>&)> f;
|
||||||
|
|
||||||
|
explicit lambda_function(std::function<T(const std::vector<runtime_value>&)> f) : f(f) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::optional<runtime_value> call(const std::vector<runtime_value>& args, base_interpreter&) {
|
||||||
|
return f(args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct lambda_function<void> : public interpreter_function {
|
||||||
|
std::function<void(const std::vector<runtime_value>&)> f;
|
||||||
|
|
||||||
|
explicit lambda_function(std::function<void(const std::vector<runtime_value>&)> f) : f(f) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<runtime_value> call(const std::vector<runtime_value>& args, base_interpreter&) override {
|
||||||
|
f(args);
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct basic_function : public interpreter_function {
|
||||||
|
const function_declaration* declaration;
|
||||||
|
|
||||||
|
explicit basic_function(const function_declaration* declaration) : declaration(declaration) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<runtime_value> call(const std::vector<runtime_value>& args, base_interpreter& i) override {
|
||||||
|
if (args.size() != declaration->parameters.size()) {
|
||||||
|
throw interpreter_error("Wrong number of arguments for function call", declaration->t);
|
||||||
|
}
|
||||||
|
|
||||||
|
i.push_function_scope();
|
||||||
|
i.push_variable_scope();
|
||||||
|
for (auto a_id = 1ULL; a_id < args.size(); a_id++) {
|
||||||
|
auto param = declaration->parameters[a_id];
|
||||||
|
auto arg = args[a_id];
|
||||||
|
|
||||||
|
//TODO: Check argument type
|
||||||
|
i.inject_value(param.name, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
i.execute_statement(*declaration->body);
|
||||||
|
|
||||||
|
std::optional<runtime_value> return_value;
|
||||||
|
auto& func_scope = i.current_function_scope();
|
||||||
|
if (func_scope.returned) {
|
||||||
|
return_value = func_scope.return_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
i.pop_variable_scope();
|
||||||
|
i.pop_function_scope();
|
||||||
|
|
||||||
|
//TODO: Check type of RVL?
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export template<statement_iterator T, std::sentinel_for<T> E>
|
||||||
|
class interpreter : public base_interpreter {
|
||||||
|
T current;
|
||||||
|
E end;
|
||||||
|
std::vector<interpreter_variable_scope> scopes;
|
||||||
|
std::unordered_map<std::string, std::unique_ptr<interpreter_function>> functions;
|
||||||
|
std::vector<interpreter_function_scope> function_scopes;
|
||||||
|
public:
|
||||||
|
constexpr std::string_view get_type_for_value(const runtime_value& v) {
|
||||||
|
auto id = v.index();
|
||||||
|
switch (id) {
|
||||||
|
case 0:
|
||||||
|
return "string";
|
||||||
|
case 1:
|
||||||
|
return "i8";
|
||||||
|
case 2:
|
||||||
|
return "i16";
|
||||||
|
case 3:
|
||||||
|
return "i32";
|
||||||
|
case 4:
|
||||||
|
return "i64";
|
||||||
|
case 5:
|
||||||
|
return "u8";
|
||||||
|
case 6:
|
||||||
|
return "u16";
|
||||||
|
case 7:
|
||||||
|
return "u32";
|
||||||
|
case 8:
|
||||||
|
return "u64";
|
||||||
|
case 9:
|
||||||
|
return "f32";
|
||||||
|
case 10:
|
||||||
|
return "f64";
|
||||||
|
case 11:
|
||||||
|
return "boolean";
|
||||||
|
default:
|
||||||
|
return "i64";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interpreter(T start, E end) : current(start), end(end) {
|
||||||
|
scopes.emplace_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_stdlib() {
|
||||||
|
functions.try_emplace("print", std::make_unique<lambda_function<void>>([](const auto& args) {
|
||||||
|
if (args.size() != 1) {
|
||||||
|
throw std::runtime_error("No arguments to print()");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& arg0 = args[0];
|
||||||
|
|
||||||
|
if (arg0.index() != 0) {
|
||||||
|
throw std::runtime_error("Invalid argument to print()");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& str = std::get<0>(arg0);
|
||||||
|
|
||||||
|
std::cout << str << std::endl;
|
||||||
|
}));
|
||||||
|
|
||||||
|
functions.insert_or_assign("boolean_to_string", std::make_unique<lambda_function<std::string>>([](const auto& args) {
|
||||||
|
if (args.size() != 1) {
|
||||||
|
throw std::runtime_error("No arguments to boolean_to_string()");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& arg0 = args[0];
|
||||||
|
|
||||||
|
if (arg0.index() != 11) {
|
||||||
|
throw std::runtime_error("Invalid argument to boolean_to_string()");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool b = std::get<11>(arg0);
|
||||||
|
|
||||||
|
return b ? "true" : "false";
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void run() {
|
||||||
|
while (current != end) {
|
||||||
|
execute_statement(**current);
|
||||||
|
++current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static runtime_value parse_literal(const expression& e) {
|
||||||
|
auto* lv = dynamic_cast<const literal_value*>(&e);
|
||||||
|
|
||||||
|
if (lv == nullptr) {
|
||||||
|
throw interpreter_error("Internal interpreter error", e.t);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& content = lv->t.content;
|
||||||
|
|
||||||
|
const auto* begin = content.data();
|
||||||
|
const auto* end = begin + content.size();
|
||||||
|
switch (lv->t.type) {
|
||||||
|
case Number:
|
||||||
|
if (content.find('.') != std::string::npos) {
|
||||||
|
double d{};
|
||||||
|
|
||||||
|
auto v = std::from_chars(begin, end, d, std::chars_format::scientific);
|
||||||
|
|
||||||
|
if (v.ec == std::errc()) {
|
||||||
|
return d;
|
||||||
|
} else {
|
||||||
|
throw interpreter_error("Failed to parse float/double value", lv->t);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uint64_t i{};
|
||||||
|
|
||||||
|
auto v = std::from_chars(begin, end, i);
|
||||||
|
|
||||||
|
if (v.ec == std::errc()) {
|
||||||
|
return i;
|
||||||
|
} else {
|
||||||
|
throw interpreter_error("Failed to parse integer value", lv->t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case String:
|
||||||
|
return content;
|
||||||
|
break;
|
||||||
|
case Boolean:
|
||||||
|
if (content == "true") {
|
||||||
|
return true;
|
||||||
|
} else if (content == "false") {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
throw interpreter_error("Invalid boolean value", lv->t);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw interpreter_error("Invalid literal value", lv->t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static runtime_value negate(runtime_value v) {
|
||||||
|
if (v.index() == 0 || v.index() == 11) {
|
||||||
|
throw std::runtime_error("Invalid literal value");
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::visit(overload {
|
||||||
|
[](std::string&) -> runtime_value { throw std::runtime_error("Invalid negation value"); },
|
||||||
|
[](bool) -> runtime_value { throw std::runtime_error("Invalid boolean value"); },
|
||||||
|
[]<typename X>(X a) -> runtime_value { return -a; }
|
||||||
|
}, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool logical_or(const dual_operation* dvo) {
|
||||||
|
auto v0 = eval_expression(*dvo->first_operand);
|
||||||
|
|
||||||
|
if (v0->index() != 11) {
|
||||||
|
throw std::runtime_error("Invalid operand type");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool b0 = std::get<11>(*v0);
|
||||||
|
|
||||||
|
if (!b0) {
|
||||||
|
auto v1 = eval_expression(*dvo->second_operand);
|
||||||
|
|
||||||
|
if (v1->index() != 11) {
|
||||||
|
throw std::runtime_error("Invalid operand type");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool b1 = std::get<11>(*v1);
|
||||||
|
|
||||||
|
return b1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool logical_and(const dual_operation* dvo) {
|
||||||
|
auto v0 = eval_expression(*dvo->first_operand);
|
||||||
|
|
||||||
|
if (!v0.has_value()) {
|
||||||
|
throw interpreter_error("Invalid operand value", dvo->first_operand->t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v0->index() != 11) {
|
||||||
|
throw interpreter_error("Invalid operand type", dvo->first_operand->t);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool b0 = std::get<11>(*v0);
|
||||||
|
|
||||||
|
if (b0) {
|
||||||
|
auto v1 = eval_expression(*dvo->second_operand);
|
||||||
|
|
||||||
|
if (!v1.has_value()) {
|
||||||
|
throw interpreter_error("Invalid operand value", dvo->second_operand->t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v1->index() != 11) {
|
||||||
|
throw interpreter_error("Invalid operand type", dvo->second_operand->t);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool b1 = std::get<11>(*v1);
|
||||||
|
|
||||||
|
return b1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime_value arithmetic_op(const dual_operation* dvo) {
|
||||||
|
auto v0 = eval_expression(*dvo->first_operand);
|
||||||
|
auto v1 = eval_expression(*dvo->second_operand);
|
||||||
|
|
||||||
|
if (!v0.has_value() || !v1.has_value()) {
|
||||||
|
throw interpreter_error("Invalid operand value", dvo->t);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::visit([&dvo]<typename A, typename B>(A&& a, B&& b) {
|
||||||
|
if (dvo->operator_token.content == "+") {
|
||||||
|
return runtime_value(arithmetic_evaluator<A, B>().sum(a, b));
|
||||||
|
} else if (dvo->operator_token.content == "-") {
|
||||||
|
return runtime_value(arithmetic_evaluator<A, B>().sub(a, b));
|
||||||
|
} else if (dvo->operator_token.content == "*") {
|
||||||
|
return runtime_value(arithmetic_evaluator<A, B>().mul(a, b));
|
||||||
|
} else if (dvo->operator_token.content == "/") {
|
||||||
|
return runtime_value(arithmetic_evaluator<A, B>().div(a, b));
|
||||||
|
} else if (dvo->operator_token.content == "%") {
|
||||||
|
return runtime_value(arithmetic_evaluator<A, B>().mod(a, b));
|
||||||
|
} else {
|
||||||
|
throw interpreter_error("Invalid arithmetic operand", dvo->operator_token);
|
||||||
|
}
|
||||||
|
}, v0.value(), v1.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime_value compare_op(const dual_operation* dvo) {
|
||||||
|
auto v0 = eval_expression(*dvo->first_operand);
|
||||||
|
auto v1 = eval_expression(*dvo->second_operand);
|
||||||
|
|
||||||
|
if (!v0.has_value() || !v1.has_value()) {
|
||||||
|
throw interpreter_error("Invalid operand value", dvo->t);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::visit([&dvo]<typename A, typename B>(A&& a, B&& b) {
|
||||||
|
if (dvo->operator_token.content == "==") {
|
||||||
|
return are_equal(a, b);
|
||||||
|
} else if (dvo->operator_token.content == "!=") {
|
||||||
|
return !are_equal(a, b);
|
||||||
|
} else {
|
||||||
|
throw interpreter_error("Invalid comparator operator", dvo->operator_token);
|
||||||
|
}
|
||||||
|
}, v0.value(), v1.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime_value order_op(const dual_operation* dvo) {
|
||||||
|
auto v0 = eval_expression(*dvo->first_operand);
|
||||||
|
auto v1 = eval_expression(*dvo->second_operand);
|
||||||
|
|
||||||
|
if (!v0.has_value() || !v1.has_value()) {
|
||||||
|
throw interpreter_error("Invalid operand value", dvo->t);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::visit([&dvo]<typename A, typename B>(A&& a, B&& b) {
|
||||||
|
if (dvo->operator_token.content == ">") {
|
||||||
|
return comparator<A, B>().more_than(a, b);
|
||||||
|
} else if (dvo->operator_token.content == "<") {
|
||||||
|
return comparator<A, B>().less_than(a, b);
|
||||||
|
} else if (dvo->operator_token.content == ">=") {
|
||||||
|
return comparator<A, B>().more_equal_than(a, b);
|
||||||
|
} else if (dvo->operator_token.content == "<=") {
|
||||||
|
return comparator<A, B>().less_equal_than(a, b);
|
||||||
|
} else {
|
||||||
|
throw interpreter_error("Invalid comparator operator", dvo->operator_token);
|
||||||
|
}
|
||||||
|
}, v0.value(), v1.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<runtime_value> eval_expression(const expression& e) {
|
||||||
|
switch (e.et) {
|
||||||
|
case EtInvalid:
|
||||||
|
throw interpreter_error("Invalid expression", e.t);
|
||||||
|
case EtLiteralValue:
|
||||||
|
return parse_literal(e);
|
||||||
|
case EtSingleValueOperation: {
|
||||||
|
auto* svo = dynamic_cast<const single_operation*>(&e);
|
||||||
|
|
||||||
|
if (svo == nullptr) {
|
||||||
|
throw interpreter_error("Internal interpreter error", e.t);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto val = eval_expression(*svo->operand);
|
||||||
|
|
||||||
|
if (!val.has_value()) {
|
||||||
|
throw interpreter_error("Invalid operand value", svo->operand->t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (svo->operator_token.content == "+") {
|
||||||
|
return val;
|
||||||
|
} else if (svo->operator_token.content == "-") {
|
||||||
|
return negate (val.value());
|
||||||
|
} else if (svo->operator_token.content == "!") {
|
||||||
|
if (val->index() != 11) {
|
||||||
|
throw interpreter_error("Invalid operand type", svo->operand->t);
|
||||||
|
} else {
|
||||||
|
return !std::get<11>(val.value());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw interpreter_error("Invalid operator", svo->operator_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EtDualValueOperation: {
|
||||||
|
auto* dvo = dynamic_cast<const dual_operation*>(&e);
|
||||||
|
|
||||||
|
if (dvo == nullptr) {
|
||||||
|
throw interpreter_error("Internal interpreter error", e.t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dvo->operator_token.content == "||") {
|
||||||
|
return logical_or(dvo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dvo->operator_token.content == "&&") {
|
||||||
|
return logical_and(dvo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dvo->operator_token.content == "+" || dvo->operator_token.content == "-"
|
||||||
|
|| dvo->operator_token.content == "*" || dvo->operator_token.content == "/"
|
||||||
|
|| dvo->operator_token.content == "%") {
|
||||||
|
return arithmetic_op(dvo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dvo->operator_token.content == "==" || dvo->operator_token.content == "!=") {
|
||||||
|
return compare_op(dvo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dvo->operator_token.content == ">" || dvo->operator_token.content == "<" ||
|
||||||
|
dvo->operator_token.content == "<=" || dvo->operator_token.content == ">=") {
|
||||||
|
return order_op(dvo);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw interpreter_error("Invalid operator", dvo->operator_token);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EtFunctionCall: {
|
||||||
|
auto* call = dynamic_cast<const function_call*>(&e);
|
||||||
|
|
||||||
|
if (call == nullptr) {
|
||||||
|
throw interpreter_error("Internal interpreter error", e.t);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = functions.find(call->function_name);
|
||||||
|
|
||||||
|
if (it == functions.end()) {
|
||||||
|
throw interpreter_error(std::format("Function {} not found", call->function_name), call->t);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<runtime_value> args;
|
||||||
|
|
||||||
|
for (auto& a : call->arguments) {
|
||||||
|
auto arg = eval_expression(*a);
|
||||||
|
|
||||||
|
if (!arg.has_value()) {
|
||||||
|
throw interpreter_error("Internal interpreter error", a->t);
|
||||||
|
}
|
||||||
|
args.emplace_back(arg.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
return it->second->call(args, *this);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EtIdentifier: {
|
||||||
|
auto* id = dynamic_cast<const identifier_expression*>(&e);
|
||||||
|
|
||||||
|
if (id == nullptr) {
|
||||||
|
throw interpreter_error("Internal interpreter error", e.t);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& s : std::ranges::reverse_view(scopes)) {
|
||||||
|
if (s.variables.contains(id->name)) {
|
||||||
|
auto& v = s.variables.at(id->name).value;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw interpreter_error("Variable not found", e.t);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<runtime_value> execute_statement(const statement& s) override {
|
||||||
|
switch (s.type) {
|
||||||
|
case Expression: {
|
||||||
|
auto* ex = dynamic_cast<const expression*>(&s);
|
||||||
|
|
||||||
|
if (ex == nullptr) {
|
||||||
|
throw interpreter_error("Internal interpreter error", s.t);
|
||||||
|
}
|
||||||
|
|
||||||
|
return eval_expression(*ex);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Block: {
|
||||||
|
auto* b = dynamic_cast<const block*>(&s);
|
||||||
|
|
||||||
|
if (b == nullptr) {
|
||||||
|
throw interpreter_error("Internal interpreter error", s.t);
|
||||||
|
}
|
||||||
|
|
||||||
|
push_variable_scope();
|
||||||
|
|
||||||
|
for (auto& st : b->statements) {
|
||||||
|
execute_statement(*st);
|
||||||
|
|
||||||
|
if (is_in_function()) {
|
||||||
|
auto& sc = current_function_scope();
|
||||||
|
if (sc.returned) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pop_variable_scope();
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ReturnStatement: {
|
||||||
|
auto* r = dynamic_cast<const return_statement*>(&s);
|
||||||
|
|
||||||
|
if (r == nullptr) {
|
||||||
|
throw interpreter_error("Internal interpreter error", s.t);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto val = r->expression != nullptr ? eval_expression(*r->expression) : std::nullopt;
|
||||||
|
|
||||||
|
auto& sc = current_function_scope();
|
||||||
|
sc.return_value = val;
|
||||||
|
sc.returned = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Root: {
|
||||||
|
auto* r = dynamic_cast<const root_statement*>(&s);
|
||||||
|
|
||||||
|
if (r == nullptr) {
|
||||||
|
throw interpreter_error("Internal interpreter error", s.t);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& st : r->statements) {
|
||||||
|
execute_statement(*st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FunctionDeclaration: {
|
||||||
|
auto* d = dynamic_cast<const function_declaration*>(&s);
|
||||||
|
|
||||||
|
if (d == nullptr) {
|
||||||
|
throw interpreter_error("Internal interpreter error", s.t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (functions.find(d->name) != functions.end()) {
|
||||||
|
throw interpreter_error("Duplicate function name", s.t);
|
||||||
|
}
|
||||||
|
|
||||||
|
functions.emplace(d->name, std::make_unique<basic_function>(d));
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case VariableDeclaration: {
|
||||||
|
auto* d = dynamic_cast<const variable_declaration*>(&s);
|
||||||
|
|
||||||
|
if (d == nullptr) {
|
||||||
|
throw interpreter_error("Internal interpreter error", s.t);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& sc = current_variable_scope();
|
||||||
|
|
||||||
|
if (sc.variables.find(d->name) != sc.variables.end()) {
|
||||||
|
throw interpreter_error("Duplicate variable name", s.t);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto val = d->initial_expression != nullptr ? eval_expression(*d->initial_expression) : throw std::runtime_error("Non-initialized variables are not supported yet."); //TODO: This
|
||||||
|
|
||||||
|
sc.variables.insert_or_assign(d->name, interpreter_variable {
|
||||||
|
d->type.has_value() ? d->type.value() : "void",
|
||||||
|
!d->constant,
|
||||||
|
val.has_value() ? val.value() : runtime_value(0),
|
||||||
|
});
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IfStatement: {
|
||||||
|
auto* is = dynamic_cast<const if_statement*>(&s);
|
||||||
|
|
||||||
|
if (is == nullptr) {
|
||||||
|
throw interpreter_error("Internal interpreter error", s.t);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto condition = eval_expression(*is->condition);
|
||||||
|
|
||||||
|
if (!condition.has_value()) {
|
||||||
|
throw interpreter_error("No condition for if statement", is->condition->t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (condition->index() != 11) {
|
||||||
|
throw interpreter_error("If condition is not boolean", is->condition->t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::get<11>(condition.value())) {
|
||||||
|
execute_statement(*is->block);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
throw interpreter_error("Internal interpreter error", s.t);
|
||||||
|
}
|
||||||
|
|
||||||
|
void push_variable_scope() override {
|
||||||
|
scopes.emplace_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_variable_scope() override {
|
||||||
|
scopes.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
void push_function_scope() override {
|
||||||
|
function_scopes.emplace_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_function_scope() override {
|
||||||
|
function_scopes.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
void inject_value(const std::string_view& name, runtime_value val) override {
|
||||||
|
auto& s = scopes.back();
|
||||||
|
|
||||||
|
auto type = get_type_for_value(val);
|
||||||
|
|
||||||
|
if (s.variables.contains(std::string(name))) {
|
||||||
|
throw std::runtime_error("Injection failed: Variable already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
s.variables.insert_or_assign(std::string(name), interpreter_variable {
|
||||||
|
std::string(type),
|
||||||
|
false,
|
||||||
|
std::move(val)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
interpreter_function_scope& current_function_scope() override {
|
||||||
|
if (function_scopes.empty()) {
|
||||||
|
throw std::runtime_error("Internal interpreter error");
|
||||||
|
}
|
||||||
|
|
||||||
|
return function_scopes.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
interpreter_variable_scope& current_variable_scope() override {
|
||||||
|
return scopes.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_in_function() override {
|
||||||
|
return !function_scopes.empty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -5,3 +5,4 @@ export import :source_stream;
|
||||||
export import :tokenizer;
|
export import :tokenizer;
|
||||||
export import :ast;
|
export import :ast;
|
||||||
export import :type_checker;
|
export import :type_checker;
|
||||||
|
export import :interpreter;
|
||||||
|
|
@ -33,6 +33,20 @@ namespace jlx {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export constexpr std::array<std::string_view, 11> stringable_types = {
|
||||||
|
"i8",
|
||||||
|
"i16",
|
||||||
|
"i32",
|
||||||
|
"i64",
|
||||||
|
"u8",
|
||||||
|
"u16",
|
||||||
|
"u32",
|
||||||
|
"u64",
|
||||||
|
"f32",
|
||||||
|
"f64",
|
||||||
|
"boolean"
|
||||||
|
};
|
||||||
|
|
||||||
class type_checker_utils {
|
class type_checker_utils {
|
||||||
static std::size_t size_of(const std::string_view& type) {
|
static std::size_t size_of(const std::string_view& type) {
|
||||||
if (type == "u8" || type == "i8" || type == "char") {
|
if (type == "u8" || type == "i8" || type == "char") {
|
||||||
|
|
@ -583,20 +597,6 @@ namespace jlx {
|
||||||
void include_stdlib() {
|
void include_stdlib() {
|
||||||
auto& glob = get_global_scope();
|
auto& glob = get_global_scope();
|
||||||
|
|
||||||
constexpr std::array<std::string_view, 11> stringable_types = {
|
|
||||||
"i8",
|
|
||||||
"i16",
|
|
||||||
"i32",
|
|
||||||
"i64",
|
|
||||||
"u8",
|
|
||||||
"u16",
|
|
||||||
"u32",
|
|
||||||
"u64",
|
|
||||||
"f32",
|
|
||||||
"f64",
|
|
||||||
"boolean"
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& type : stringable_types) {
|
for (const auto& type : stringable_types) {
|
||||||
glob.functions.insert_or_assign(std::format("{}_to_string", type), runtime_function{
|
glob.functions.insert_or_assign(std::format("{}_to_string", type), runtime_function{
|
||||||
"string",
|
"string",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue