From 7c1612d9c2452f7451e9ca76a62c940a876ffe86 Mon Sep 17 00:00:00 2001 From: John Stefanelli Date: Sat, 31 May 2025 19:43:46 +0200 Subject: [PATCH] [libjlx] First successful "execution" --- jlx/src/main.cpp | 10 +- libjlx/CMakeLists.txt | 1 + libjlx/modules/ast.cppm | 3 +- libjlx/modules/interpreter.cppm | 859 +++++++++++++++++++++++++++++++ libjlx/modules/main.cppm | 1 + libjlx/modules/type_checker.cppm | 28 +- 6 files changed, 881 insertions(+), 21 deletions(-) create mode 100644 libjlx/modules/interpreter.cppm diff --git a/jlx/src/main.cpp b/jlx/src/main.cpp index e68997f..82feaf4 100644 --- a/jlx/src/main.cpp +++ b/jlx/src/main.cpp @@ -12,7 +12,6 @@ int main(int argc, char** argv) { std::istreambuf_iterator start, end; if (argc > 1) { if (std::filesystem::is_regular_file(argv[1])) { - std::cout << "Opening: " << argv[1] << std::endl; file = std::ifstream(argv[1]); start = std::istreambuf_iterator(file); } else { @@ -37,8 +36,6 @@ int main(int argc, char** argv) { } } while (res.has_value()); - std::cout << "Read " << tokens.size() << " tokens\n"; - auto last = std::string(); while(res.has_value()) { const auto& t = res.value(); @@ -61,8 +58,6 @@ int main(int argc, char** argv) { return 1; } - std::cout << "Parsed " << rt->statements.size() << " statements" << std::endl; - auto type_checker = jlx::type_checker(rt->statements.begin(), rt->statements.end()); type_checker.include_stdlib(); try { @@ -72,7 +67,10 @@ int main(int argc, char** argv) { 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; } diff --git a/libjlx/CMakeLists.txt b/libjlx/CMakeLists.txt index da8ce16..a31a378 100644 --- a/libjlx/CMakeLists.txt +++ b/libjlx/CMakeLists.txt @@ -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/utils.cppm" "${CMAKE_CURRENT_SOURCE_DIR}/modules/type_checker.cppm" + "${CMAKE_CURRENT_SOURCE_DIR}/modules/interpreter.cppm" ) target_compile_options(libjlx PRIVATE $,/W4 /WX,-Wall -Wextra -Werror>) target_include_directories(libjlx PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") diff --git a/libjlx/modules/ast.cppm b/libjlx/modules/ast.cppm index 1abcf4c..1db0d81 100644 --- a/libjlx/modules/ast.cppm +++ b/libjlx/modules/ast.cppm @@ -409,7 +409,6 @@ namespace jlx { auto start = *current; - switch(current->type) { case Identifier: if (previous != nullptr) { @@ -512,6 +511,8 @@ namespace jlx { fail_invalid_token(*current); break; } + + fail_invalid_token(*current); } std::unique_ptr parse_function() { diff --git a/libjlx/modules/interpreter.cppm b/libjlx/modules/interpreter.cppm new file mode 100644 index 0000000..00644f8 --- /dev/null +++ b/libjlx/modules/interpreter.cppm @@ -0,0 +1,859 @@ +module; + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +export module jlx:interpreter; +import :ast; +import :type_checker; + +namespace jlx { + using runtime_value = std::variant; + + struct interpreter_variable { + std::string type; + bool writable; + runtime_value value; + }; + + struct interpreter_variable_scope { + std::unordered_map variables; + }; + + struct interpreter_function_scope { + std::optional 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 + struct overload : Ts... { using Ts::operator()...; }; + + struct base_interpreter { + virtual std::optional 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 call(const std::vector&, base_interpreter&) = 0; + + virtual ~interpreter_function() = default; + }; + + template + 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::is_specialized; + std::numeric_limits::is_specialized; + std::numeric_limits::is_integer == std::numeric_limits::is_integer; + std::numeric_limits::is_signed == std::numeric_limits::is_signed; + }; + + template + concept Comparable = requires { + requires std::is_same_v || + requires (A a, B b) + { + requires std::numeric_limits::is_signed == std::numeric_limits::is_signed; + { a == b } -> std::convertible_to; + { a != b } -> std::convertible_to; + }; + }; + + template + concept Orderable = requires + { + requires std::is_same_v || requires (A a, B b){ + requires std::numeric_limits>::is_signed == std::numeric_limits>::is_signed; + { a < b } -> std::convertible_to; + { a > b } -> std::convertible_to; + { a >= b } -> std::convertible_to; + { a <= b } -> std::convertible_to; + }; + }; + + static_assert(!Operable); + static_assert(Operable); + static_assert(!Operable); + static_assert(!Comparable); + static_assert(!Comparable); + static_assert(!Orderable); + static_assert(!Orderable); + + template + struct arithmetic_evaluator { + std::remove_cvref_t sum(A a, B b) requires Operable { + return a + b; + } + + std::remove_cvref_t mul(A a, B b) requires Operable { + return a * b; + } + + std::remove_cvref_t div(A a, B b) requires Operable { + return a / b; + } + + std::remove_cvref_t sub(A a, B b) requires Operable { + return a - b; + } + + std::remove_cvref_t mod(A a, B b) requires Operable { + return a % b; + } + + std::remove_cvref_t sum(A a, B b) requires Operable && (!Operable) { + return a + b; + } + + std::remove_cvref_t mul(A a, B b) requires Operable && (!Operable) { + return a * b; + } + + std::remove_cvref_t div(A a, B b) requires Operable && (!Operable) { + return a / b; + } + + std::remove_cvref_t sub(A a, B b) requires Operable && (!Operable) { + return a - b; + } + + std::remove_cvref_t mod(A a, B b) requires Operable && (!Operable) { + return a % b; + } + + std::string sum(std::string a, B b) requires (!std::is_same_v, 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::string> && !Operable && !Operable) { + 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 requires Comparable + bool are_equal(A a, B b) { + return a == b; + } + + template + [[noreturn]] bool are_equal(A, B) { + throw std::runtime_error("Non-comparable types"); + } + + template + struct comparator { + bool more_than(A a, B b) requires Orderable { + return a > b; + } + + bool less_than(A a, B b) requires Orderable { + return a < b; + } + + bool more_equal_than(A a, B b) requires Orderable { + return a >= b; + } + + bool less_equal_than(A a, B b) requires Orderable { + 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 + struct lambda_function : public interpreter_function { + std::function&)> f; + + explicit lambda_function(std::function&)> f) : f(f) { + + } + + virtual std::optional call(const std::vector& args, base_interpreter&) { + return f(args); + } + }; + + + template<> + struct lambda_function : public interpreter_function { + std::function&)> f; + + explicit lambda_function(std::function&)> f) : f(f) { + + } + + std::optional call(const std::vector& 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 call(const std::vector& 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 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 E> + class interpreter : public base_interpreter { + T current; + E end; + std::vector scopes; + std::unordered_map> functions; + std::vector 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>([](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>([](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(&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"); }, + [](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](A&& a, B&& b) { + if (dvo->operator_token.content == "+") { + return runtime_value(arithmetic_evaluator().sum(a, b)); + } else if (dvo->operator_token.content == "-") { + return runtime_value(arithmetic_evaluator().sub(a, b)); + } else if (dvo->operator_token.content == "*") { + return runtime_value(arithmetic_evaluator().mul(a, b)); + } else if (dvo->operator_token.content == "/") { + return runtime_value(arithmetic_evaluator().div(a, b)); + } else if (dvo->operator_token.content == "%") { + return runtime_value(arithmetic_evaluator().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](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](A&& a, B&& b) { + if (dvo->operator_token.content == ">") { + return comparator().more_than(a, b); + } else if (dvo->operator_token.content == "<") { + return comparator().less_than(a, b); + } else if (dvo->operator_token.content == ">=") { + return comparator().more_equal_than(a, b); + } else if (dvo->operator_token.content == "<=") { + return comparator().less_equal_than(a, b); + } else { + throw interpreter_error("Invalid comparator operator", dvo->operator_token); + } + }, v0.value(), v1.value()); + } + + std::optional 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(&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(&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(&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 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(&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 execute_statement(const statement& s) override { + switch (s.type) { + case Expression: { + auto* ex = dynamic_cast(&s); + + if (ex == nullptr) { + throw interpreter_error("Internal interpreter error", s.t); + } + + return eval_expression(*ex); + } + break; + case Block: { + auto* b = dynamic_cast(&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(&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(&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(&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(d)); + + return std::nullopt; + } + break; + case VariableDeclaration: { + auto* d = dynamic_cast(&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(&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(); + } + }; +} \ No newline at end of file diff --git a/libjlx/modules/main.cppm b/libjlx/modules/main.cppm index 4bd9954..a075631 100644 --- a/libjlx/modules/main.cppm +++ b/libjlx/modules/main.cppm @@ -5,3 +5,4 @@ export import :source_stream; export import :tokenizer; export import :ast; export import :type_checker; +export import :interpreter; \ No newline at end of file diff --git a/libjlx/modules/type_checker.cppm b/libjlx/modules/type_checker.cppm index 14f5771..1722614 100644 --- a/libjlx/modules/type_checker.cppm +++ b/libjlx/modules/type_checker.cppm @@ -33,6 +33,20 @@ namespace jlx { } }; + export constexpr std::array stringable_types = { + "i8", + "i16", + "i32", + "i64", + "u8", + "u16", + "u32", + "u64", + "f32", + "f64", + "boolean" + }; + class type_checker_utils { static std::size_t size_of(const std::string_view& type) { if (type == "u8" || type == "i8" || type == "char") { @@ -583,20 +597,6 @@ namespace jlx { void include_stdlib() { auto& glob = get_global_scope(); - constexpr std::array stringable_types = { - "i8", - "i16", - "i32", - "i64", - "u8", - "u16", - "u32", - "u64", - "f32", - "f64", - "boolean" - }; - for (const auto& type : stringable_types) { glob.functions.insert_or_assign(std::format("{}_to_string", type), runtime_function{ "string",