From 0ba3e4324d4e82079c4f8a91351402bb3a311e4c Mon Sep 17 00:00:00 2001 From: lisyarus Date: Mon, 29 Dec 2025 14:42:25 +0300 Subject: [PATCH] Interpreter clean-up (wip - identifier/function resolving is broken) --- apps/interpreter/source/main.cpp | 19 +- examples/test.psl | 16 + libs/ast/source/print.cpp | 1 + libs/ast/source/type_check.cpp | 2 +- .../include/pslang/interpreter/context.hpp | 6 +- .../include/pslang/interpreter/error.hpp | 20 ++ .../include/pslang/interpreter/eval.hpp | 3 - libs/interpreter/source/context.cpp | 6 +- libs/interpreter/source/eval.cpp | 329 ++++++------------ libs/interpreter/source/exec.cpp | 98 ++++-- plans.txt | 2 +- 11 files changed, 224 insertions(+), 278 deletions(-) create mode 100644 libs/interpreter/include/pslang/interpreter/error.hpp diff --git a/apps/interpreter/source/main.cpp b/apps/interpreter/source/main.cpp index e2bdbe3..1920f2e 100644 --- a/apps/interpreter/source/main.cpp +++ b/apps/interpreter/source/main.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -114,24 +115,36 @@ int main(int argc, char ** argv) ast::check_and_infer_types(ast); parsed.push_back(std::move(ast)); } - catch (pslang::ast::parse_error const & error) + catch (ast::parse_error const & error) { std::cerr << "Parse error at " << error.location() << ":\n " << error.what() << std::endl; print_error_context(argv[arg], error.location()); return EXIT_FAILURE; } - catch (pslang::ast::type_error const & error) + catch (ast::type_error const & error) { std::cerr << "Type error at " << error.location() << ":\n " << error.what() << std::endl; print_error_context(argv[arg], error.location()); return EXIT_FAILURE; } - catch (pslang::ast::invalid_ast_error const & error) + catch (ast::invalid_ast_error const & error) { std::cerr << "Invalid AST at " << error.location() << ":\n " << error.what() << std::endl; print_error_context(argv[arg], error.location()); return EXIT_FAILURE; } + catch (interpreter::internal_error const & error) + { + std::cerr << "Internal error at " << error.location() << ":\n " << error.what() << std::endl; + print_error_context(argv[arg], error.location()); + return EXIT_FAILURE; + } + catch (interpreter::runtime_error const & error) + { + std::cerr << "Runtime error at " << error.location() << ":\n " << error.what() << std::endl; + print_error_context(argv[arg], error.location()); + return EXIT_FAILURE; + } } if (dump_ast) diff --git a/examples/test.psl b/examples/test.psl index 26ed9a7..b48b8e9 100644 --- a/examples/test.psl +++ b/examples/test.psl @@ -29,3 +29,19 @@ func fib(n : u32) -> u32: return fib(n - 1u) + fib(n - 2u) let fib10 = fib(10u) + + +func h() -> u32: + return 0u + +func f() -> u32: + return h() + +func g() -> u32: + func h() -> u32: + return 1u + return f() + +// Should equal 0u, but equals 1u due to an error in +// how the interpreter resolves functions & variables +let x = g() diff --git a/libs/ast/source/print.cpp b/libs/ast/source/print.cpp index e2ce63c..28408ca 100644 --- a/libs/ast/source/print.cpp +++ b/libs/ast/source/print.cpp @@ -343,6 +343,7 @@ namespace pslang::ast put_indent(out, options); out << "body\n"; child(*node.statements); + --options.indent_level; } void apply(return_statement const & node) diff --git a/libs/ast/source/type_check.cpp b/libs/ast/source/type_check.cpp index 9c8b1ea..26fbc99 100644 --- a/libs/ast/source/type_check.cpp +++ b/libs/ast/source/type_check.cpp @@ -346,7 +346,7 @@ namespace pslang::ast types::print(os, *ftype->arguments[i]); os << " but got type "; types::print(os, *arg_type); - throw type_error(os.str(), node.location); + throw type_error(os.str(), get_location(*node.arguments[i])); } } diff --git a/libs/interpreter/include/pslang/interpreter/context.hpp b/libs/interpreter/include/pslang/interpreter/context.hpp index ae7c03d..64bd54e 100644 --- a/libs/interpreter/include/pslang/interpreter/context.hpp +++ b/libs/interpreter/include/pslang/interpreter/context.hpp @@ -30,7 +30,7 @@ namespace pslang::interpreter std::vector fields; }; - struct scope + struct frame { std::unordered_map variables; std::unordered_map structs; @@ -40,14 +40,14 @@ namespace pslang::interpreter return variables.count(name) > 0 || structs.count(name) > 0; } - bool is_function_scope = false; + types::type_ptr expected_return_type = nullptr; value return_value = unit_value{}; }; struct context { bool trace = false; - std::vector scope_stack; + std::vector frame_stack; }; context empty_context(); diff --git a/libs/interpreter/include/pslang/interpreter/error.hpp b/libs/interpreter/include/pslang/interpreter/error.hpp new file mode 100644 index 0000000..a1c3d61 --- /dev/null +++ b/libs/interpreter/include/pslang/interpreter/error.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace pslang::interpreter +{ + + struct internal_error + : ast::error + { + using error::error; + }; + + struct runtime_error + : ast::error + { + using error::error; + }; + +} diff --git a/libs/interpreter/include/pslang/interpreter/eval.hpp b/libs/interpreter/include/pslang/interpreter/eval.hpp index 0142fd6..393f05b 100644 --- a/libs/interpreter/include/pslang/interpreter/eval.hpp +++ b/libs/interpreter/include/pslang/interpreter/eval.hpp @@ -9,10 +9,7 @@ namespace pslang::interpreter { - types::type resolve_type(context & context, ast::type const & type); - value eval(context & context, ast::expression_ptr const & expression); - value * eval_ref(context & context, ast::expression_ptr const & expression); } diff --git a/libs/interpreter/source/context.cpp b/libs/interpreter/source/context.cpp index 0036ea1..53d4743 100644 --- a/libs/interpreter/source/context.cpp +++ b/libs/interpreter/source/context.cpp @@ -41,7 +41,7 @@ namespace pslang::interpreter value apply(types::named_type const & named_type) { - auto const & struct_data = context.scope_stack.at(named_type.level).structs.at(named_type.name); + auto const & struct_data = context.frame_stack.at(named_type.level).structs.at(named_type.name); struct_value result{.struct_type = std::make_unique(named_type)}; for (auto const & field : struct_data.fields) @@ -55,13 +55,13 @@ namespace pslang::interpreter context empty_context() { context result; - result.scope_stack.emplace_back(); + result.frame_stack.emplace_back(); return result; } void dump(std::ostream & out, context const & context) { - for (auto const & scope : context.scope_stack) + for (auto const & scope : context.frame_stack) { for (auto const & variable : scope.variables) { diff --git a/libs/interpreter/source/eval.cpp b/libs/interpreter/source/eval.cpp index 2de4731..7fce78e 100644 --- a/libs/interpreter/source/eval.cpp +++ b/libs/interpreter/source/eval.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -13,107 +14,7 @@ namespace pslang::interpreter namespace { - types::type resolve_type_impl(context &, types::unit_type const & type) - { - return type; - } - - types::type resolve_type_impl(context &, types::primitive_type const & type) - { - return type; - } - - types::type resolve_type_impl(context & context, ast::array_type const & type) - { - return types::array_type{std::make_unique(resolve_type(context, *type.element_type)), type.size}; - } - - types::type resolve_type_impl(context & context, ast::function_type const & type) - { - types::function_type result; - for (auto const & argument : type.arguments) - result.arguments.push_back(std::make_unique(resolve_type(context, *argument))); - result.result = std::make_unique(resolve_type(context, *type.result)); - return result; - } - - types::type resolve_type_impl(context & context, ast::type_identifier const & type) - { - return types::named_type{type.name, type.level}; - } - - types::type resolve_type_impl(context & context, ast::type const & type) - { - return std::visit([&](auto const & type){ return resolve_type_impl(context, type); }, type); - } - - void print(std::ostream & out, ast::unary_operation_type type) - { - switch (type) - { - case ast::unary_operation_type::negation: - out << "-"; - return; - case ast::unary_operation_type::logical_not: - out << "!"; - return; - } - - out << "(unknown)"; - } - - void print(std::ostream & out, ast::binary_operation_type type) - { - switch (type) - { - case ast::binary_operation_type::addition: - out << "+"; - return; - case ast::binary_operation_type::subtraction: - out << "-"; - return; - case ast::binary_operation_type::multiplication: - out << "*"; - return; - case ast::binary_operation_type::division: - out << "/"; - return; - case ast::binary_operation_type::remainder: - out << "%"; - return; - case ast::binary_operation_type::logical_and: - out << "&"; - return; - case ast::binary_operation_type::logical_or: - out << "|"; - return; - case ast::binary_operation_type::logical_xor: - out << "^"; - return; - case ast::binary_operation_type::equals: - out << "=="; - return; - case ast::binary_operation_type::not_equals: - out << "!="; - return; - case ast::binary_operation_type::less: - out << "<"; - return; - case ast::binary_operation_type::greater: - out << ">"; - return; - case ast::binary_operation_type::less_equals: - out << "<="; - return; - case ast::binary_operation_type::greater_equals: - out << ">="; - return; - } - - out << "(unknown)"; - } - - std::uint64_t get_array_index(value const & index, std::uint64_t size) + std::uint64_t get_array_index(value const & index, std::uint64_t size, ast::location const & location) { std::optional index_unsigned; std::optional index_signed; @@ -159,7 +60,7 @@ namespace pslang::interpreter std::ostringstream os; os << "Cannot index into an array with an expression of type "; types::print(os, type_of(index)); - throw std::runtime_error(os.str()); + throw internal_error(os.str(), location); } if (index_unsigned) @@ -168,7 +69,7 @@ namespace pslang::interpreter { std::ostringstream os; os << "Array index " << *index_unsigned << " out of bounds " << size; - throw std::runtime_error(os.str()); + throw runtime_error(os.str(), location); } return *index_unsigned; } @@ -178,7 +79,7 @@ namespace pslang::interpreter { std::ostringstream os; os << "Array index " << *index_signed << " out of bounds " << size; - throw std::runtime_error(os.str()); + throw runtime_error(os.str(), location); } return *index_signed; } @@ -199,28 +100,26 @@ namespace pslang::interpreter value eval_impl(context & context, ast::identifier const & identifier) { - for (auto it = context.scope_stack.rbegin(); it != context.scope_stack.rend(); ++it) - { + // NB: cannot use identifier.level here because lexical scope stack + // almost always differs from execution frame stack + for (auto it = context.frame_stack.rbegin(); it != context.frame_stack.rend(); ++it) if (auto jt = it->variables.find(identifier.name); jt != it->variables.end()) return jt->second.value; - } - throw std::runtime_error("Identifier \"" + identifier.name + "\" is not defined"); + throw internal_error("Identifier \"" + identifier.name + "\" is not defined", identifier.location); } template - value unary_operation_impl(ast::unary_operation_type type, Value const & value) + value unary_operation_impl(ast::unary_operation_type type, Value const & value, ast::location const & location) { std::ostringstream os; - os << "Cannot apply unary operator \""; - print(os, type); - os << "\" to a value of type "; + os << "Cannot apply " << type << " to a value of type "; types::print(os, type_of(value)); - throw std::runtime_error(os.str()); + throw internal_error(os.str(), location); } template - value unary_operation_impl(ast::unary_operation_type type, primitive_value_base const & arg1) + value unary_operation_impl(ast::unary_operation_type type, primitive_value_base const & arg1, ast::location const & location) { switch (type) { @@ -243,22 +142,20 @@ namespace pslang::interpreter } std::ostringstream os; - os << "Cannot apply unary operator \""; - print(os, type); - os << "\" to a value of type "; + os << "Cannot apply " << type << " to a value of type "; types::print(os, type_of(primitive_value(arg1))); - throw std::runtime_error(os.str()); + throw internal_error(os.str(), location); } - value unary_operation_impl(ast::unary_operation_type type, primitive_value const & arg1) + value unary_operation_impl(ast::unary_operation_type type, primitive_value const & arg1, ast::location const & location) { - return std::visit([&](auto const & value){ return unary_operation_impl(type, value); }, arg1); + return std::visit([&](auto const & value){ return unary_operation_impl(type, value, location); }, arg1); } value eval_impl(context & context, ast::unary_operation const & unary_operation) { auto arg1 = eval_impl(context, unary_operation.arg1); - return std::visit([&](auto const & value){ return unary_operation_impl(unary_operation.type, value); }, arg1); + return std::visit([&](auto const & value){ return unary_operation_impl(unary_operation.type, value, unary_operation.location); }, arg1); } bool requires_same_argument_type(ast::binary_operation_type) @@ -268,7 +165,7 @@ namespace pslang::interpreter } template - value binary_operation_impl_same_type(ast::binary_operation_type type, primitive_value_base const & arg1, value const & arg2_generic) + value binary_operation_impl_same_type(ast::binary_operation_type type, primitive_value_base const & arg1, value const & arg2_generic, ast::location const & location) { primitive_value_base const & arg2 = std::get>(std::get(arg2_generic)); @@ -359,31 +256,27 @@ namespace pslang::interpreter } std::ostringstream os; - os << "Cannot apply binary operator \""; - print(os, type); - os << "\" to values of type "; + os << "Cannot apply " << type << " to values of type "; types::print(os, type_of(primitive_value(arg1))); os << " and "; types::print(os, type_of(primitive_value(arg2))); - throw std::runtime_error(os.str()); + throw internal_error(os.str(), location); } template - value binary_operation_impl_same_type(ast::binary_operation_type type, Value const & arg1, value const & arg2) + value binary_operation_impl_same_type(ast::binary_operation_type type, Value const & arg1, value const & arg2, ast::location const & location) { std::ostringstream os; - os << "Cannot apply binary operator \""; - print(os, type); - os << "\" to values of type "; + os << "Cannot apply " << type << " to values of type "; types::print(os, type_of(arg1)); os << " and "; types::print(os, type_of(arg2)); - throw std::runtime_error(os.str()); + throw internal_error(os.str(), location); } - value binary_operation_impl_same_type(ast::binary_operation_type type, primitive_value const & arg1, value const & arg2) + value binary_operation_impl_same_type(ast::binary_operation_type type, primitive_value const & arg1, value const & arg2, ast::location const & location) { - return std::visit([&](auto const & value){ return binary_operation_impl_same_type(type, value, arg2); }, arg1); + return std::visit([&](auto const & value){ return binary_operation_impl_same_type(type, value, arg2, location); }, arg1); } value eval_impl(context & context, ast::binary_operation const & binary_operation) @@ -399,45 +292,43 @@ namespace pslang::interpreter if (!types::equal(type1, type2)) { std::ostringstream os; - os << "Cannot apply binary operator \""; - print(os, binary_operation.type); - os << "\" to values of type "; + os << "Cannot apply " << binary_operation.type << " to values of type "; types::print(os, type1); os << " and "; types::print(os, type2); - throw std::runtime_error(os.str()); + throw internal_error(os.str(), binary_operation.location); } - return std::visit([&](auto const & value){ return binary_operation_impl_same_type(binary_operation.type, value, arg2); }, arg1); + return std::visit([&](auto const & value){ return binary_operation_impl_same_type(binary_operation.type, value, arg2, binary_operation.location); }, arg1); } - throw std::runtime_error("eval(binary_operation) for different argument types not implemented"); + throw internal_error("eval(binary_operation) for different argument types not implemented", binary_operation.location); } - value cast_impl(unit_value const & value, types::type const & type) + value cast_impl(unit_value const & value, types::type const & type, ast::location const & location) { if (types::equal(type, types::unit_type{})) return value; - throw std::runtime_error("Cannot cast unit type to anything"); + throw internal_error("Cannot cast unit type to anything", location); } - value cast_impl(array_value const & value, types::type const & type) + value cast_impl(array_value const & value, types::type const & type, ast::location const & location) { if (types::equal(type, type_of(value))) return value; - throw std::runtime_error("Cannot cast array type to anything"); + throw internal_error("Cannot cast array type to anything", location); } template - value cast_impl(primitive_value_base const & value, types::unit_type const &) + value cast_impl(primitive_value_base const & value, types::unit_type const &, ast::location const & location) { - throw std::runtime_error("Cannot cast anything to unit type"); + throw internal_error("Cannot cast anything to unit type", location); } template - value cast_impl(primitive_value_base const & value, types::primitive_type_base const & type) + value cast_impl(primitive_value_base const & value, types::primitive_type_base const & type, ast::location const & location) { if constexpr (std::is_same_v) { @@ -453,56 +344,56 @@ namespace pslang::interpreter types::print(os, type_of(primitive_value(value))); os << " to type "; types::print(os, types::primitive_type(type)); - throw std::runtime_error(os.str()); + throw internal_error(os.str(), location); } template - value cast_impl(primitive_value_base const & value, types::primitive_type const & type) + value cast_impl(primitive_value_base const & value, types::primitive_type const & type, ast::location const & location) { - return std::visit([&](auto const & type){ return cast_impl(value, type); }, type); + return std::visit([&](auto const & type){ return cast_impl(value, type, location); }, type); } template - value cast_impl(primitive_value_base const &, types::array_type const &) + value cast_impl(primitive_value_base const &, types::array_type const &, ast::location const & location) { - throw std::runtime_error("Cannot cast anything to array type"); + throw internal_error("Cannot cast anything to array type", location); } template - value cast_impl(primitive_value_base const &, types::function_type const &) + value cast_impl(primitive_value_base const &, types::function_type const &, ast::location const & location) { - throw std::runtime_error("Cannot cast anything to function type"); + throw internal_error("Cannot cast anything to function type", location); } template - value cast_impl(primitive_value_base const & value, types::type const & type) + value cast_impl(primitive_value_base const & value, types::type const & type, ast::location const & location) { - return std::visit([&](auto const & type){ return cast_impl(value, type); }, type); + return std::visit([&](auto const & type){ return cast_impl(value, type, location); }, type); } - value cast_impl(primitive_value const & value, types::type const & type) + value cast_impl(primitive_value const & value, types::type const & type, ast::location const & location) { - return std::visit([&](auto const & value){ return cast_impl(value, type); }, value); + return std::visit([&](auto const & value){ return cast_impl(value, type, location); }, value); } - value cast_impl(struct_value const & value, types::type const & type) + value cast_impl(struct_value const & value, types::type const & type, ast::location const & location) { if (types::equal(type, types::unit_type{})) return value; - throw std::runtime_error("Cannot cast struct type to anything"); + throw internal_error("Cannot cast struct type to anything", location); } - value cast_impl(function_value const &, types::type const &) + value cast_impl(function_value const &, types::type const &, ast::location const & location) { - throw std::runtime_error("Cannot cast function type to anything"); + throw internal_error("Cannot cast function type to anything", location); } value eval_impl(context & context, ast::cast_operation const & cast_operation) { auto arg = eval(context, cast_operation.expression); - auto type = resolve_type(context, *cast_operation.type); - return std::visit([&](auto const & value){ return cast_impl(value, type); }, arg); + auto type = get_type(*cast_operation.type); + return std::visit([&](auto const & value){ return cast_impl(value, *type, cast_operation.location); }, arg); } value eval_impl(context & context, ast::function_call const & function_call) @@ -517,7 +408,7 @@ namespace pslang::interpreter { std::ostringstream os; os << "Cannot call function: expected " << fvalue->arguments.size() << " arguments, got " << function_call.arguments.size(); - throw std::runtime_error(os.str()); + throw internal_error(os.str(), function_call.location); } std::vector args; @@ -534,33 +425,21 @@ namespace pslang::interpreter types::print(os, *fvalue->arguments[i].type); os << " but actual type is "; types::print(os, actual_type); - throw std::runtime_error(os.str()); + throw internal_error(os.str(), ast::get_location(*function_call.arguments[i])); } } - auto & function_scope = context.scope_stack.emplace_back(); - function_scope.is_function_scope = true; + auto & function_scope = context.frame_stack.emplace_back(); for (std::size_t i = 0; i < args.size(); ++i) function_scope.variables[fvalue->arguments[i].name] = {.category = ast::value_category::constant, .value = std::move(args[i])}; - auto expected_return_type = fvalue->return_type; + function_scope.expected_return_type = fvalue->return_type; exec(context, fvalue->statements); - auto actual_return_type = type_of(context.scope_stack.back().return_value); - if (!types::equal(actual_return_type, *expected_return_type)) - { - std::ostringstream os; - os << "Error returning from function: expected return type is "; - types::print(os, *expected_return_type); - os << " but actual type is "; - types::print(os, actual_return_type); - throw std::runtime_error(os.str()); - } - - auto result = std::move(context.scope_stack.back().return_value); - context.scope_stack.pop_back(); + auto result = std::move(context.frame_stack.back().return_value); + context.frame_stack.pop_back(); return result; } @@ -568,7 +447,7 @@ namespace pslang::interpreter os << "Cannot call "; print(os, lvalue); os << ": not a function"; - throw std::runtime_error(os.str()); + throw internal_error(os.str(), function_call.location); } else if (function_call.type) { @@ -582,16 +461,23 @@ namespace pslang::interpreter os << "Cannot create built-in type "; types::print(os, *type); os << ": expected 0 arguments, but got " << function_call.arguments.size(); - throw std::runtime_error(os.str()); + throw internal_error(os.str(), function_call.location); } else if (auto named_type = std::get_if(type.get())) { - auto const & scope = context.scope_stack.at(named_type->level); + auto const & scope = context.frame_stack.at(named_type->level); auto const & data = scope.structs.at(named_type->name); if (function_call.arguments.empty()) return zero_value(context, *type); + if (data.fields.size() != function_call.arguments.size()) + { + std::ostringstream os; + os << "Cannot create struct \"" << named_type->name << "\": expected " << data.fields.size() << " arguments, got " << function_call.arguments.size(); + throw internal_error(os.str(), function_call.location); + } + std::vector args; for (auto const & expression : function_call.arguments) args.push_back(eval(context, expression)); @@ -599,9 +485,7 @@ namespace pslang::interpreter std::unordered_map fields; for (std::size_t i = 0; i < args.size(); ++i) - { fields[data.fields[i].name] = std::make_unique(std::move(args[i])); - } return struct_value{ .struct_type = type, @@ -609,16 +493,16 @@ namespace pslang::interpreter }; } else - throw std::runtime_error("Unknown type in constructor"); + throw internal_error("Unknown type in constructor", function_call.location); } else - throw std::runtime_error("Function call node has neither function nor type"); + throw internal_error("Function call node has neither function nor type", function_call.location); } value eval_impl(context & context, ast::array const & array) { if (array.elements.empty()) - throw std::runtime_error("Internal error: array ast node cannot have zero elements"); + throw internal_error("Array node cannot have zero elements", array.location); types::type_ptr element_type; std::vector elements; @@ -637,7 +521,7 @@ namespace pslang::interpreter types::print(os, *element_type); os << " but element #" << i << " type is "; types::print(os, new_type); - throw std::runtime_error(os.str()); + throw internal_error(os.str(), array.location); } } @@ -653,14 +537,12 @@ namespace pslang::interpreter auto index = eval(context, array_access.index); if (auto avalue = std::get_if(&array)) - { - return *avalue->elements[get_array_index(index, avalue->elements.size())]; - } + return *avalue->elements[get_array_index(index, avalue->elements.size(), array_access.location)]; std::ostringstream os; os << "Cannot index into a non-array of type "; types::print(os, type_of(array)); - throw std::runtime_error(os.str()); + throw internal_error(os.str(), array_access.location); } value eval_impl(context & context, ast::field_access const & field_access) @@ -675,14 +557,14 @@ namespace pslang::interpreter os << "Struct "; types::print(os, type_of(object)); os << " has no field named \"" << field_access.field_name << "\""; - throw std::runtime_error(os.str()); + throw internal_error(os.str(), field_access.location); } std::ostringstream os; os << "Value of type "; types::print(os, type_of(object)); os << " is not a struct"; - throw std::runtime_error(os.str()); + throw internal_error(os.str(), field_access.location); } value eval_impl(context & context, ast::expression_ptr const & expression) @@ -690,50 +572,48 @@ namespace pslang::interpreter return std::visit([&](auto const & expression){ return eval_impl(context, expression); }, *expression); } - value * eval_ref_impl(context & context, ast::literal const &) + value * eval_ref_impl(context & context, ast::literal const & literal) { - throw std::runtime_error("Literal cannot be on the left-hand-side of assignment"); + throw internal_error("Literal cannot be on the left-hand-side of assignment", ast::get_location(literal)); } value * eval_ref_impl(context & context, ast::identifier const & identifier) { - for (auto it = context.scope_stack.rbegin(); it != context.scope_stack.rend(); ++it) - { - if (auto jt = it->variables.find(identifier.name); jt != it->variables.end()) - { - if (jt->second.category != ast::value_category::_mutable) - throw std::runtime_error("Cannot assign a value to a non-mutable variable"); + if (identifier.level >= context.frame_stack.size()) + throw internal_error("Bad identifier level", identifier.location); - return &jt->second.value; - } - } + auto & scope = context.frame_stack[identifier.level]; - throw std::runtime_error("Identifier \"" + identifier.name + "\" is not defined"); + auto it = scope.variables.find(identifier.name); + if (it == scope.variables.end()) + throw internal_error("Identifier \"" + identifier.name + "\" is not defined", identifier.location); + + return &it->second.value; } - value * eval_ref_impl(context & context, ast::unary_operation const &) + value * eval_ref_impl(context & context, ast::unary_operation const & unary_operation) { - throw std::runtime_error("Unary operation cannot be on the left-hand-side of assignment"); + throw internal_error("Unary operation cannot be on the left-hand-side of assignment", unary_operation.location); } - value * eval_ref_impl(context & context, ast::binary_operation const &) + value * eval_ref_impl(context & context, ast::binary_operation const & binary_operation) { - throw std::runtime_error("Binary operation cannot be on the left-hand-side of assignment"); + throw internal_error("Binary operation cannot be on the left-hand-side of assignment", binary_operation.location); } - value * eval_ref_impl(context & context, ast::cast_operation const &) + value * eval_ref_impl(context & context, ast::cast_operation const & cast_operation) { - throw std::runtime_error("Cast operation cannot be on the left-hand-side of assignment"); + throw internal_error("Cast operation cannot be on the left-hand-side of assignment", cast_operation.location); } - value * eval_ref_impl(context & context, ast::function_call const &) + value * eval_ref_impl(context & context, ast::function_call const & function_call) { - throw std::runtime_error("Function call cannot be on the left-hand-side of assignment"); + throw internal_error("Function call cannot be on the left-hand-side of assignment", function_call.location); } - value * eval_ref_impl(context & context, ast::array const &) + value * eval_ref_impl(context & context, ast::array const & array) { - throw std::runtime_error("Array cannot be on the left-hand-side of assignment"); + throw internal_error("Array cannot be on the left-hand-side of assignment", array.location); } value * eval_ref_impl(context & context, ast::array_access const & array_access) @@ -743,13 +623,13 @@ namespace pslang::interpreter if (auto avalue = std::get_if(array_ref)) { - return avalue->elements[get_array_index(index, avalue->elements.size())].get(); + return avalue->elements[get_array_index(index, avalue->elements.size(), ast::get_location(*array_access.index))].get(); } std::ostringstream os; os << "Cannot index into a non-array of type "; types::print(os, type_of(*array_ref)); - throw std::runtime_error(os.str()); + throw internal_error(os.str(), array_access.location); } value * eval_ref_impl(context & context, ast::field_access const & field_access) @@ -764,14 +644,14 @@ namespace pslang::interpreter os << "Struct "; types::print(os, type_of(*object_ref)); os << " has no field named \"" << field_access.field_name << "\""; - throw std::runtime_error(os.str()); + throw internal_error(os.str(), field_access.location); } std::ostringstream os; os << "Value of type "; types::print(os, type_of(*object_ref)); os << " is not a struct"; - throw std::runtime_error(os.str()); + throw internal_error(os.str(), field_access.location); } value * eval_ref_impl(context & context, ast::expression_ptr const & expression) @@ -781,11 +661,6 @@ namespace pslang::interpreter } - types::type resolve_type(context & context, ast::type const & type) - { - return resolve_type_impl(context, type); - } - value eval(context & context, ast::expression_ptr const & expression) { return eval_impl(context, expression); diff --git a/libs/interpreter/source/exec.cpp b/libs/interpreter/source/exec.cpp index d7e9572..79fe91a 100644 --- a/libs/interpreter/source/exec.cpp +++ b/libs/interpreter/source/exec.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -23,7 +24,7 @@ namespace pslang::interpreter ~stack_pop_guard() { - context_.scope_stack.pop_back(); + context_.frame_stack.pop_back(); } private: @@ -50,7 +51,7 @@ namespace pslang::interpreter types::print(os, new_type); os << " to a variable of type "; types::print(os, existing_type); - throw std::runtime_error(os.str()); + throw internal_error(os.str(), assignment.location); } *ref = std::move(new_value); @@ -58,41 +59,41 @@ namespace pslang::interpreter void exec_impl(context & context, ast::variable_declaration const & variable_declaration) { - auto & scope = context.scope_stack.back(); + auto & scope = context.frame_stack.back(); if (scope.contains(variable_declaration.name)) - throw std::runtime_error("Identifier \"" + variable_declaration.name + "\" is already defined in this scope"); + throw internal_error("Identifier \"" + variable_declaration.name + "\" is already defined in this scope", variable_declaration.location); auto value = eval(context, variable_declaration.initializer); if (variable_declaration.type) { - auto expected_type = resolve_type(context, *variable_declaration.type); + auto expected_type = get_type(*variable_declaration.type); auto actual_type = type_of(value); - if (!types::equal(expected_type, actual_type)) + if (!types::equal(*expected_type, actual_type)) { std::ostringstream os; os << "Cannot initialize a variable of type "; - types::print(os, expected_type); + types::print(os, *expected_type); os << " with an expression of type "; types::print(os, actual_type); - throw std::runtime_error(os.str()); + throw internal_error(os.str(), variable_declaration.location); } } - context.scope_stack.back().variables[variable_declaration.name] = {.category = variable_declaration.category, .value = value}; + context.frame_stack.back().variables[variable_declaration.name] = {.category = variable_declaration.category, .value = value}; } - void exec_impl(context & context, ast::if_block const &) + void exec_impl(context & context, ast::if_block const & if_block) { - throw std::runtime_error("Internal interpreter error: if blocks cannot be present in the final AST"); + throw internal_error("if blocks cannot be present in the final AST", if_block.location); } - void exec_impl(context & context, ast::else_block const &) + void exec_impl(context & context, ast::else_block const & else_block) { - throw std::runtime_error("Internal interpreter error: else blocks cannot be present in the final AST"); + throw internal_error("else blocks cannot be present in the final AST", else_block.location); } - void exec_impl(context & context, ast::else_if_block const &) + void exec_impl(context & context, ast::else_if_block const & else_if_block) { - throw std::runtime_error("Internal interpreter error: else if blocks cannot be present in the final AST"); + throw internal_error("else if blocks cannot be present in the final AST", else_if_block.location); } void exec_impl(context & context, ast::if_chain const & if_chain) @@ -111,7 +112,7 @@ namespace pslang::interpreter os << "Expected type bool, got type "; types::print(os, actual_type); os << " in if block condition"; - throw std::runtime_error(os.str()); + throw internal_error(os.str(), get_location(*block.condition)); } do_exec = std::get(std::get(value)).value; @@ -120,7 +121,7 @@ namespace pslang::interpreter if (!do_exec) continue; - context.scope_stack.emplace_back(); + context.frame_stack.emplace_back(); stack_pop_guard guard(context); exec(context, block.statements); break; @@ -139,12 +140,12 @@ namespace pslang::interpreter os << "Expected type bool, got type "; types::print(os, actual_type); os << " in while block condition"; - throw std::runtime_error(os.str()); + throw internal_error(os.str(), get_location(*while_block.condition)); } if (std::get(std::get(value)).value) { - context.scope_stack.emplace_back(); + context.frame_stack.emplace_back(); stack_pop_guard guard(context); exec(context, while_block.statements); } @@ -155,55 +156,78 @@ namespace pslang::interpreter void exec_impl(context & context, ast::function_definition const & function_definition) { - auto & scope = context.scope_stack.back(); - if (scope.contains(function_definition.name)) + auto & frame = context.frame_stack.back(); + if (frame.contains(function_definition.name)) throw std::runtime_error("Identifier \"" + function_definition.name + "\" is already defined in this scope"); function_value value; for (auto const & argument : function_definition.arguments) - value.arguments.push_back({.name = argument.name, .type = std::make_unique(resolve_type(context, *argument.type))}); - value.return_type = std::make_unique(resolve_type(context, *function_definition.return_type)); + value.arguments.push_back({.name = argument.name, .type = get_type(*argument.type)}); + value.return_type = get_type(*function_definition.return_type); value.statements = function_definition.statements; - scope.variables[function_definition.name] = {.category = ast::value_category::constant, .value = std::move(value)}; + frame.variables[function_definition.name] = {.category = ast::value_category::constant, .value = std::move(value)}; } void exec_impl(context & context, ast::return_statement const & return_statement) { - auto value = return_statement.value ? eval(context, return_statement.value) : unit_value{}; - for (auto it = context.scope_stack.rbegin(); it != context.scope_stack.rend(); ++it) + // NB: cannot use return_statement.level here because lexical scope stack + // almost always differs from execution frame stack + + // NB: cannot use iterators here because they might get invalidated during eval() + + for (std::size_t n = context.frame_stack.size(); n --> 0;) { - if (it->is_function_scope) { - it->return_value = std::move(value); - throw return_exception{}; + auto & frame = context.frame_stack[n]; + + if (!frame.expected_return_type) + continue; } + + auto value = return_statement.value ? eval(context, return_statement.value) : unit_value{}; + auto actual_type = type_of(value); + + auto & frame = context.frame_stack[n]; + + if (!types::equal(*frame.expected_return_type, actual_type)) + { + std::ostringstream os; + os << "Returning value of type "; + types::print(os, actual_type); + os << " from a function returning "; + types::print(os, *frame.expected_return_type); + throw internal_error(os.str(), return_statement.location); + } + + frame.return_value = std::move(value); + throw return_exception{}; } - throw std::runtime_error("Cannot return outside of a function"); + throw internal_error("Cannot return outside of function scope", return_statement.location); } void exec_impl(context & context, ast::field_definition const & field_definition) { - throw std::runtime_error("Internal interpreter error: field definitions cannot be present in the final AST outside of struct definitions"); + throw internal_error("Field definitions cannot be present in the final AST outside of struct definitions", field_definition.location); } void exec_impl(context & context, ast::struct_definition const & struct_definition) { - auto & scope = context.scope_stack.back(); - if (scope.contains(struct_definition.name)) - throw std::runtime_error("Identifier \"" + struct_definition.name + "\" is already defined in this scope"); + auto & frame = context.frame_stack.back(); + if (frame.contains(struct_definition.name)) + throw internal_error("Identifier \"" + struct_definition.name + "\" is already defined in this scope", struct_definition.location); struct_data result; for (auto const & field : struct_definition.fields) { result.fields.push_back({ .name = field.name, - .type = std::make_unique(resolve_type(context, *field.type)), + .type = get_type(*field.type), }); } - scope.structs[struct_definition.name] = std::move(result); + frame.structs[struct_definition.name] = std::move(result); } void exec_impl(context & context, ast::statement_list_ptr const & statements) @@ -216,7 +240,7 @@ namespace pslang::interpreter } catch (return_exception const &) { - if (context.scope_stack.back().is_function_scope) + if (context.frame_stack.back().expected_return_type) break; else throw; diff --git a/plans.txt b/plans.txt index f1cf79d..7dff4cd 100644 --- a/plans.txt +++ b/plans.txt @@ -1,5 +1,5 @@ Future plans: -* Clean up interpreter: don't repeat checks done in type checker, throw internal errors when appropriate +* Fix interpreter identifier resolution for functions & variables * Pointers: pointer types, address-of operator (&), dereferencing, scope-based lifetime tracking in interpreter * Function overloading: separate functions from values (again) in interpreter, allow casting to specific function type to take function value * C FFI: `foreign func sin(x : f32) -> f32`