From 1f8071c89b83c945af93e7b4b17fc2cee30ccdd6 Mon Sep 17 00:00:00 2001 From: programsnail Date: Sun, 21 Apr 2024 07:33:14 +0300 Subject: [PATCH] day1 progress: typecheck template, mode check for unique --- .gitignore | 2 + CMakeLists.txt | 21 ++++ compile_commands.json | 20 ++++ include/mode_check.hpp | 2 + include/parsing_tree.hpp | 114 +++++++++++++++++++ include/type_check.hpp | 0 src/main.cpp | 6 + src/mode_check.cpp | 236 +++++++++++++++++++++++++++++++++++++++ src/parsing_tree.cpp | 1 + src/type_check.cpp | 112 +++++++++++++++++++ tests/tests.cpp | 11 ++ 11 files changed, 525 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 compile_commands.json create mode 100644 include/mode_check.hpp create mode 100644 include/parsing_tree.hpp create mode 100644 include/type_check.hpp create mode 100644 src/main.cpp create mode 100644 src/mode_check.cpp create mode 100644 src/parsing_tree.cpp create mode 100644 src/type_check.cpp create mode 100644 tests/tests.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9785597 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +.cache diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e10ebd3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.10) + +project(Lang) + +set(CMAKE_C_COMPILER clang) +set(CMAKE_CXX_COMPILER clang++) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +set(CMAKE_CXX_STANDARD 20) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") + +include_directories( + include +) + +add_executable(lang src/main.cpp + src/parsing_tree.cpp + src/type_check.cpp + src/mode_check.cpp) diff --git a/compile_commands.json b/compile_commands.json new file mode 100644 index 0000000..c2db892 --- /dev/null +++ b/compile_commands.json @@ -0,0 +1,20 @@ +[ +{ + "directory": "/home/dragon/Containers/Lang/mini-rust/build", + "command": "clang++ -I/home/dragon/Containers/Lang/mini-rust/include -Wall -Wextra -std=gnu++20 -o CMakeFiles/lang.dir/src/main.cpp.o -c /home/dragon/Containers/Lang/mini-rust/src/main.cpp", + "file": "/home/dragon/Containers/Lang/mini-rust/src/main.cpp", + "output": "CMakeFiles/lang.dir/src/main.cpp.o" +}, +{ + "directory": "/home/dragon/Containers/Lang/mini-rust/build", + "command": "clang++ -I/home/dragon/Containers/Lang/mini-rust/include -Wall -Wextra -std=gnu++20 -o CMakeFiles/lang.dir/src/parsing_tree.cpp.o -c /home/dragon/Containers/Lang/mini-rust/src/parsing_tree.cpp", + "file": "/home/dragon/Containers/Lang/mini-rust/src/parsing_tree.cpp", + "output": "CMakeFiles/lang.dir/src/parsing_tree.cpp.o" +}, +{ + "directory": "/home/dragon/Containers/Lang/mini-rust/build", + "command": "clang++ -I/home/dragon/Containers/Lang/mini-rust/include -Wall -Wextra -std=gnu++20 -o CMakeFiles/lang.dir/src/typechecker.cpp.o -c /home/dragon/Containers/Lang/mini-rust/src/typechecker.cpp", + "file": "/home/dragon/Containers/Lang/mini-rust/src/typechecker.cpp", + "output": "CMakeFiles/lang.dir/src/typechecker.cpp.o" +} +] \ No newline at end of file diff --git a/include/mode_check.hpp b/include/mode_check.hpp new file mode 100644 index 0000000..3f59c93 --- /dev/null +++ b/include/mode_check.hpp @@ -0,0 +1,2 @@ +#pragma once + diff --git a/include/parsing_tree.hpp b/include/parsing_tree.hpp new file mode 100644 index 0000000..78a5cd6 --- /dev/null +++ b/include/parsing_tree.hpp @@ -0,0 +1,114 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace types { + +using namespace std; + +struct Type; +using TypePtr = shared_ptr; + +struct ArrowType { + vector types; +}; + +struct BoolType {}; +struct IntType {}; +// struct UnitType {}; + +struct AnyType {}; + +struct Type { + static constexpr size_t ARROW_TYPE_INDEX = 0; + variant type; + + enum class Loc { GLOBAL, LOCAL } loc = Loc::GLOBAL; + enum class Uniq { SHARED, UNIQUE, EXCL } uniq = Uniq::SHARED; + enum class Lin { MANY, ONCE, SEP } lin = Lin::MANY; +}; + +template +Type make_type(Args&&... args) { + return Type{T{std::forward(args)...}}; +} + +} // namespace types + +namespace nodes { + +using namespace std; + +struct Node { + optional type = std::nullopt; +}; + +struct Expr; +using ExprPtr = shared_ptr; +using ExprPtrV = std::vector; + +struct Arg : public Node { + string name; +}; + +struct Const : public Node { + int value; +}; + +struct Var : public Node { + string name; +}; + +struct Let : public Node { + Arg name; + ExprPtr body; + ExprPtr where; +}; + +struct Lambda : public Node { + + vector args; + ExprPtr expr; +}; + +struct Call : public Node { + ExprPtr func; + vector args; +}; + +struct Condition : public Node { + ExprPtr condition; + ExprPtr then_case; + ExprPtr else_case; +}; + +// struct FunctionDecl { +// string name; +// vector args; +// ExprPtr expr; +// }; + +struct Expr { + variant value; +}; + +template +ExprPtr make_expr(Args&&... args) { + return std::make_shared(T{std::forward(args)...}); +} + +static ExprPtr lambda1(string name, ExprPtr expr) { + return make_expr(vector{{name}}, std::move(expr)); +} + +ExprPtr operator_call(string name, ExprPtr left, ExprPtr right) { + return make_expr(make_expr(name), ExprPtrV{left, right}); +} + +// TODO: all constructors + +} // namespace nodes diff --git a/include/type_check.hpp b/include/type_check.hpp new file mode 100644 index 0000000..e69de29 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..8b13b77 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,6 @@ +#include "parsing_tree.hpp" +#include "typechecker.hpp" + +int main() { + return 0; +} diff --git a/src/mode_check.cpp b/src/mode_check.cpp new file mode 100644 index 0000000..4eedeff --- /dev/null +++ b/src/mode_check.cpp @@ -0,0 +1,236 @@ +#include "mode_check.hpp" + +#include "parsing_tree.hpp" + +#include +#include + +namespace mode_check { + +// C++ 23 +[[noreturn]] inline void unreachable() { + // Uses compiler specific extensions if possible. + // Even if no extension is used, undefined behavior is still raised by + // an empty function body and the noreturn attribute. +#if defined(_MSC_VER) && !defined(__clang__) // MSVC + __assume(false); +#else // GCC, Clang + __builtin_unreachable(); +#endif +} + +using namespace types; + +struct VarState { + Type type; + size_t count = 0; +}; + +struct ModeError { + enum Error { + UNKNOWN, + NO_VAR, + NO_VAR_TYPE, + NO_TYPE, + WRONG_TYPE, + LOCAL, + UNIQUE, + EXCL, + ONCE, + SEP + } error = UNKNOWN; + + source_location location; + + ModeError(Error error, source_location location) + : type(type), location(location) {} +}; + +struct State { + friend struct Context; + + State() { vars_stack.emplace_back(); } + + void set_error(ModeError::Error error, + source_location location = source_location::current()) { + if (first_error.has_value()) { + return; + } + + first_error = ModeError(std::move(error), location); + } + + std::optional get_var_state(const std::string &name, + bool last_context_only = false) { + for (auto vars_it = vars_stack.rbegin(); vars_it != vars_stack.rend(); + ++vars_it) { + auto var_it = vars_it->find(name); + if (var_it == vars_it->end()) { + if (last_context_only) { + break; + } + + continue; + } + return &var_it->second; + } + + set_error(ModeError::NO_VAR); + return std::nullopt; + } + + void add_var(std::string name, Type type) { + vars_stack.back().insert({std::move(name), VarState{std::move(type)}}); + // TODO: check existance + } + + std::optional get_first_error() { return first_error; } + +private: + void enter_context() { vars_stack.emplace_back(); } + + void exit_context() { vars_stack.pop_back(); } + +private: + vector> vars_stack; + std::optional first_error; +}; + +struct Context { + Context(State &state) : state_(state) { state_.enter_context(); } + + ~Context() { state_.exit_context(); } + +private: + State &state_; +}; + +// struct ExclVarScope { +// ExclVarScope(std::string name, State &state) +// : name_(std::move(name)), state_(state) {} + +// ~ExclVarScope() {} + +// private: +// std::string name_; +// State &state_; +// }; + +void check_expr(nodes::ExprPtr expr, State &state); + +void check_const(const nodes::Const &, State &) {} + +void check_var(const nodes::Var &expr, State &state) { + if (not expr.type.has_value()) { + state.set_error(ModeError::NO_TYPE); + return; + } + auto type = expr.type.value(); + + if (auto maybe_var_state = state.get_var_state(expr.name); + maybe_var_state.has_value()) { + auto &var_state = *maybe_var_state.value(); + + if (var_state.type.uniq == Type::Uniq::UNIQUE) { + ++var_state.count; + if (var_state.count > 1 || type.uniq != Type::Uniq::UNIQUE) { + state.set_error(ModeError::UNIQUE); + return; + } + } + } + // TODO: other modes +} + +void check_let(const nodes::Let &expr, State &state) { + { + Context context(state); + check_expr(expr.body, state); + } + + { + Context context(state); + + if (not expr.name.type.has_value()) { + state.set_error(ModeError::NO_VAR_TYPE); + } + state.add_var(expr.name.name, expr.name.type.value()); + + check_expr(expr.where, state); + } +} + +void check_lambda(const nodes::Lambda &expr, State &state) { + Context context(state); + + for (const auto &arg : expr.args) { + if (not arg.type.has_value()) { + state.set_error(ModeError::NO_VAR_TYPE); + continue; + } + state.add_var(arg.name, arg.type.value()); + } + + check_expr(expr.expr, state); +} + +void check_call(const nodes::Call &expr, State &state) { + // if (not expr.type.has_value()) { + // state.set_error(ModeError::NO_TYPE); + // return; + // } + // auto type = expr.type.value(); + + // if (not holds_alternative(type.type)) { + // state.set_error(ModeError::WRONG_TYPE); + // return; + // } + + // const auto &arrow_type = get(type.type); + + // if (arrow_type.types.size() != expr.args.size() + 1) { + // state.set_error(ModeError::WRONG_TYPE); + // return; + // } + + // not required, because types are propogated to Vars in type check + + check_expr(expr.func, state); + + for (const auto &arg : expr.args) { + check_expr(arg, state); + } +} + +void check_condition(const nodes::Condition &expr, State &state) { + check_expr(expr.condition, state); + check_expr(expr.then_case, state); + check_expr(expr.else_case, state); +} + +void check_expr(nodes::ExprPtr expr, State &state) { + switch (expr->value.index()) { + case 0: // Const + check_const(std::get<0>(expr->value), state); + break; + case 1: // Var + check_var(std::get<1>(expr->value), state); + break; + case 2: // Let + check_let(std::get<2>(expr->value), state); + break; + case 3: // Lambda + check_lambda(std::get<3>(expr->value), state); + break; + case 4: // Call + check_call(std::get<4>(expr->value), state); + break; + case 5: // Condition + check_condition(std::get<5>(expr->value), state); + break; + default: + unreachable(); + } +} + +} // namespace mode_check diff --git a/src/parsing_tree.cpp b/src/parsing_tree.cpp new file mode 100644 index 0000000..c1a3c22 --- /dev/null +++ b/src/parsing_tree.cpp @@ -0,0 +1 @@ +#include "parsing_tree.hpp" diff --git a/src/type_check.cpp b/src/type_check.cpp new file mode 100644 index 0000000..76b2464 --- /dev/null +++ b/src/type_check.cpp @@ -0,0 +1,112 @@ +#include "type_check.hpp" + +#include "parsing_tree.hpp" + +// TODO + +namespace type_check { + +using namespace types; + +// C++ 23 +[[noreturn]] inline void unreachable() { + // Uses compiler specific extensions if possible. + // Even if no extension is used, undefined behavior is still raised by + // an empty function body and the noreturn attribute. +#if defined(_MSC_VER) && !defined(__clang__) // MSVC + __assume(false); +#else // GCC, Clang + __builtin_unreachable(); +#endif +} + +// bool eq(Type x, Type y) { +// if (x.type.index() != y.type.index()) { +// return false; +// } + +// if (x.type.index() != Type::ARROW_TYPE_INDEX) { +// return true; +// } + +// const auto &x_types = std::get(x.type); +// const auto &y_types = std::get(y.type); + +// if (x_types.size() != ) +// } + +// ----------------- + +template +using Typechecked = std::pair; + +Typechecked typecheck_expr(nodes::ExprPtr expr); + +Typechecked typecheck_const(nodes::Const expr) { + // TODO + return {std::move(expr), make_type()}; +} + +Typechecked typecheck_var(nodes::Var expr) { + // TODO + return {std::move(expr), make_type()}; +} + +Typechecked typecheck_let(nodes::Let expr) { + // TODO + return {std::move(expr), make_type()}; +} + +Typechecked typecheck_lambda(nodes::Lambda expr) { + // TODO + return {std::move(expr), make_type()}; +} + +Typechecked typecheck_call(nodes::Call expr) { + // TODO + return {std::move(expr), make_type()}; +} + +Typechecked typecheck_condition(nodes::Condition expr) { + // const auto [condition_expr, condition_type] = typecheck_expr(expr.condition); + // expr.condition = std::move(condition_expr); + + // const auto [then_case_expr, then_case_type] = typecheck_expr(expr.then_case); + // expr.then_case = std::move(then_case_expr); + + // const auto [else_case_expr, else_type_type] = typecheck_expr(expr.else_case); + // expr.else_case = std::move(else_case_expr); + + return {std::move(expr), make_type()}; +} + +Typechecked typecheck_expr(nodes::ExprPtr expr) { + types::Type type; + + switch (expr->value.index()) { + case 0: // Const + std::tie(expr->value, type) = typecheck_const(std::get<0>(expr->value)); + break; + case 1: // Var + std::tie(expr->value, type) = typecheck_var(std::get<1>(expr->value)); + break; + case 2: // Let + std::tie(expr->value, type) = typecheck_let(std::get<2>(expr->value)); + break; + case 3: // Lambda + std::tie(expr->value, type) = typecheck_lambda(std::get<3>(expr->value)); + break; + case 4: // Call + std::tie(expr->value, type) = typecheck_call(std::get<4>(expr->value)); + break; + case 5: // Condition + std::tie(expr->value, type) = typecheck_condition(std::get<5>(expr->value)); + break; + default: + unreachable(); + } + + return {std::move(expr), std::move(type)}; +} + +} // namespace type_check diff --git a/tests/tests.cpp b/tests/tests.cpp new file mode 100644 index 0000000..048667e --- /dev/null +++ b/tests/tests.cpp @@ -0,0 +1,11 @@ +#include "parsing_tree.hpp" + +using namespace nodes; + +int main() { + const auto program = + Expr{Let{Arg{"f", {}}, + lambda1("x", operator_call("+", make_expr("x"), + make_expr("x"))), + make_expr("f")}}; +}