diff --git a/CMakeLists.txt b/CMakeLists.txt index a5576f1..5e8713f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,5 +2,5 @@ cmake_minimum_required(VERSION 3.0) project(lily) set(CMAKE_CXX_STANDARD 14) -add_executable(lily src/main.cpp src/parser.cpp src/parser.c src/type.cpp src/type_manager.cpp) +add_executable(lily src/main.cpp src/parser.cpp src/parser.c src/type.cpp src/type_manager.cpp src/ast.cpp src/type_checker.cpp) target_include_directories(lily PUBLIC src) diff --git a/src/ast.cpp b/src/ast.cpp new file mode 100644 index 0000000..403ddf7 --- /dev/null +++ b/src/ast.cpp @@ -0,0 +1,73 @@ +#include "ast.hpp" +#include "type_checker.hpp" +#include "error.hpp" + +namespace lily { + type* ast_num::check(type_manager& mgr, std::shared_ptr env) { + std::cout << "checking number" << std::endl; + return mgr.require_type("Int"); + } + + type* ast_var::check(type_manager& mgr, std::shared_ptr env) { + std::cout << "checking variable" << std::endl; + return env->get_identifier_type(name); + } + + type* ast_app::check(type_manager& mgr, std::shared_ptr env) { + std::cout << "checking application" << std::endl; + type* ltype = left->check(mgr, env); + type* rtype = right->check(mgr, env); + + // We LHS has to be a function, so unify LHS with that. + type_func* f = mgr.create_type(nullptr, nullptr); + // Input type of function is known - it's rtype. + f->left = rtype; + // Output is not known - create parameter for it. + type* new_param = mgr.create_type(); + f->right = new_param; + + // Unify ltype with the function (we know it has to be one!) + if(!ltype->unify_with(f)) + throw error("unable to unify application type"); + + return new_param; + } + + type* ast_op::check(type_manager& mgr, std::shared_ptr env) { + type* ltype = left->check(mgr, env); + type* rtype = right->check(mgr, env); + + // We know the thing has to be a nunmber, so we unify with number type. + type* inttype = mgr.require_type("Int"); + if(!ltype->unify_with(inttype)) + throw error("left hand side of operator must be a number."); + if(!rtype->unify_with(inttype)) + throw error("right hand side of operator must be a number."); + + // We return an integer. + return inttype; + } + + type* ast_let::check(type_manager& mgr, std::shared_ptr env) { + if(env->identifier_exists(name)) + throw error("invalid redefinition of variable."); + type* etype = expr->check(mgr, env); + auto new_env = env->with_type(name, etype); + return in->check(mgr, new_env); + } + + type* ast_letrec::check(type_manager& mgr, std::shared_ptr env) { + if(env->identifier_exists(name)) + throw error("invalid redefinition of variable."); + type* variable_type = mgr.create_type(); + auto new_env = env->with_type(name, variable_type); + type* etype = expr->check(mgr, new_env); + if(!variable_type->unify_with(etype)) + throw error("incompatible type for variable"); + return in->check(mgr, new_env); + } + + type* ast_case::check(type_manager& mgr, std::shared_ptr env) { + throw error("unimplemented"); + } +} diff --git a/src/ast.hpp b/src/ast.hpp index 0086c58..0604aac 100644 --- a/src/ast.hpp +++ b/src/ast.hpp @@ -2,10 +2,15 @@ #include #include #include "pattern.hpp" +#include "type.hpp" +#include "type_manager.hpp" namespace lily { + class type_env; + struct ast { virtual ~ast() = default; + virtual type* check(type_manager& mgr, std::shared_ptr env) = 0; }; typedef std::unique_ptr ast_ptr; @@ -15,6 +20,7 @@ namespace lily { ast_num(int i) : num(i) {} ~ast_num() = default; + type* check(type_manager& mgr, std::shared_ptr env); }; struct ast_var : ast { @@ -23,6 +29,7 @@ namespace lily { ast_var(std::string n) : name(std::move(n)) {} ~ast_var() = default; + type* check(type_manager& mgr, std::shared_ptr env); }; struct ast_app : ast { @@ -32,6 +39,7 @@ namespace lily { ast_app(ast_ptr l, ast_ptr r) : left(std::move(l)), right(std::move(r)) {} ~ast_app() = default; + type* check(type_manager& mgr, std::shared_ptr env); }; struct ast_op : ast { @@ -47,6 +55,7 @@ namespace lily { ast_op(enum op o, ast_ptr l, ast_ptr r) : op(o), left(std::move(l)), right(std::move(r)) {} ~ast_op() = default; + type* check(type_manager& mgr, std::shared_ptr env); }; struct ast_let : ast { @@ -57,12 +66,14 @@ namespace lily { ast_let(std::string n, ast_ptr e, ast_ptr i) : name(std::move(n)), expr(std::move(e)), in(std::move(i)) {} ~ast_let() = default; + type* check(type_manager& mgr, std::shared_ptr env); }; struct ast_letrec : ast_let { ast_letrec(std::string n, ast_ptr e, ast_ptr i) : ast_let(std::move(n), std::move(e), std::move(i)) {} ~ast_letrec() = default; + type* check(type_manager& mgr, std::shared_ptr env); }; struct ast_case : ast { @@ -75,5 +86,6 @@ namespace lily { std::vector branches; ~ast_case() = default; + type* check(type_manager& mgr, std::shared_ptr env); }; } diff --git a/src/main.cpp b/src/main.cpp index 7afe916..f2a6908 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,7 +8,7 @@ int main() { "data Bool = { True, False }\n" "data Color = { Red, Black }\n" "data IntList = { Nil, Cons(Int, IntList) }\n" - "defn add x y = { x + y }"); + "defn add x y = { x + add x x }"); } catch(lily::error& e) { std::cout << e.message << std::endl; } diff --git a/src/parser.cpp b/src/parser.cpp index c34d4e6..0116f04 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -3,6 +3,7 @@ extern "C" { } #include "parser.hpp" #include "pattern.hpp" +#include "type_checker.hpp" #include namespace lily { @@ -238,7 +239,47 @@ namespace lily { } program_ptr prog = build_program(into, s.c_str()); + prog->check(); pgs_free_tree(into); return prog; } + + void program::check() { + // Each function has an environment, which will be used + // as base for type checking. + std::map> function_envs; + auto base_env = std::make_shared(); + + // First step is to collect all function types. + for(auto& pair : functions) { + // Create a local function environment. + auto function_env = std::make_shared(); + function_env->set_parent(base_env.get()); + function_envs[pair.first] = function_env; + + // We'll be building up the function type. + // Create the return type parameter + type* return_type = type_mgr.create_type(); + type* current_type = return_type; + + // Create type parameters for every variable + // We also want to place them in a local environment + for(int i = 0; i < pair.second.params.size(); i++) { + const std::string& str = + pair.second.params[pair.second.params.size() - i - 1]; + type* variable_param = type_mgr.create_type(); + function_env->set_type(str, variable_param); + current_type = + type_mgr.create_type(variable_param, current_type); + } + + // Store function in env. + base_env->set_type(pair.first, current_type); + } + + // Now that we have collected the functions, check their bodies. + for(auto& pair : functions) { + pair.second.body->check(type_mgr, function_envs[pair.first]); + } + } } diff --git a/src/parser.hpp b/src/parser.hpp index efbc2ea..23cd90a 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -11,6 +11,8 @@ namespace lily { struct program { type_manager type_mgr; std::map functions; + + void check(); }; typedef std::unique_ptr program_ptr; diff --git a/src/type.cpp b/src/type.cpp index 0925c25..d1aea8f 100644 --- a/src/type.cpp +++ b/src/type.cpp @@ -1,4 +1,5 @@ #include "type.hpp" +#include "error.hpp" namespace lily { type_data::constructor* type_data::create_constructor(const std::string& name, @@ -12,4 +13,49 @@ namespace lily { constructors.push_back(std::move(new_constructor)); return raw_ptr; } + + bool try_unify_pram(type* substitute_with, type* substitute_in) { + type_parameter* param = dynamic_cast(substitute_in); + if(!param) return false; + if(!param->actual_type) { + param->actual_type = substitute_with; + return true; + } else { + return substitute_with->unify_with(param->actual_type); + } + } + + bool type_internal::unify_with(type* other) { + type_internal* other_int = dynamic_cast(other); + if(other_int && other_int->type_id == type_id) return true; + if(try_unify_pram(this, other)) return true; + return false; + } + + bool type_parameter::unify_with(type* other) { + if(this == other) return true; + if(!actual_type) { + actual_type = other; + return true; + } + if(try_unify_pram(this, other)) return true; + if(actual_type->unify_with(other)) return true; + return false; + } + + bool type_data::unify_with(type* other) { + type_data* other_data = dynamic_cast(other); + if(other_data && other_data->type_id == type_id) return true; + if(try_unify_pram(this, other)) return true; + return false; + } + + bool type_func::unify_with(type* other) { + type_func* other_func = dynamic_cast(other); + if(try_unify_pram(this, other)) return true; + if(other_func && + left->unify_with(other_func->left) && + right->unify_with(other_func->right)) return true; + return false; + } } diff --git a/src/type.hpp b/src/type.hpp index acef15a..66a0133 100644 --- a/src/type.hpp +++ b/src/type.hpp @@ -11,12 +11,19 @@ namespace lily { struct type { virtual ~type() = default; + virtual bool unify_with(type* other) = 0; }; struct type_internal : type { int type_id; type_internal(int id) : type_id(id) {} + bool unify_with(type* other); + }; + + struct type_parameter : type { + type* actual_type = nullptr; + bool unify_with(type* other); }; struct type_data : type { @@ -31,6 +38,7 @@ namespace lily { type_data(int id) : type_id(id) {} constructor* create_constructor(const std::string& name, std::vector&& params); + bool unify_with(type* other); }; struct type_func : type { @@ -39,6 +47,7 @@ namespace lily { type_func(type* l, type* r) : left(l), right(r) {} + bool unify_with(type* other); }; } diff --git a/src/type_checker.cpp b/src/type_checker.cpp new file mode 100644 index 0000000..b73fc1e --- /dev/null +++ b/src/type_checker.cpp @@ -0,0 +1,32 @@ +#include "type_checker.hpp" +#include "error.hpp" + +namespace lily { + bool type_env::identifier_exists(const std::string& name) { + return identifier_types.count(name) != 0 || (parent && parent->identifier_exists(name)); + } + + type* type_env::get_identifier_type(const std::string& name) { + std::cout << "looking up " << name << std::endl; + if(!identifier_types.count(name)) { + if(parent) return parent->get_identifier_type(name); + throw error("unknown variable"); + } + return identifier_types[name]; + } + + std::shared_ptr type_env::with_type(const std::string& name, type* ntype) { + auto child_env = std::make_shared(); + child_env->parent = this; + child_env->identifier_types[name] = ntype; + return child_env; + } + + void type_env::set_type(const std::string& name, type* ntype) { + identifier_types[name] = ntype; + } + + void type_env::set_parent(type_env* parent) { + this->parent = parent; + } +} diff --git a/src/type_checker.hpp b/src/type_checker.hpp new file mode 100644 index 0000000..e909b67 --- /dev/null +++ b/src/type_checker.hpp @@ -0,0 +1,22 @@ +#pragma once +#include +#include "type.hpp" +#include "ast.hpp" + +namespace lily { + class type_env { + private: + type_env* parent = nullptr; + std::map identifier_types; + + public: + bool identifier_exists(const std::string& name); + type* get_identifier_type(const std::string& name); + void unify(type* l, type* r); + + std::shared_ptr with_type(const std::string& name, type* ntype); + void set_type(const std::string& name, type* ntype); + + void set_parent(type_env* new_parent); + }; +} diff --git a/src/type_manager.cpp b/src/type_manager.cpp index e495f8e..92b9c58 100644 --- a/src/type_manager.cpp +++ b/src/type_manager.cpp @@ -34,8 +34,8 @@ namespace lily { return raw_ptr; } - type* type_manager::require_type(const std::string& name) { + type* type_manager::require_type(const std::string& name) const { if(!type_names.count(name)) throw error("invalid type name"); - return type_names[name]; + return type_names.find(name)->second; } } diff --git a/src/type_manager.hpp b/src/type_manager.hpp index 5b45984..71e406e 100644 --- a/src/type_manager.hpp +++ b/src/type_manager.hpp @@ -16,7 +16,14 @@ namespace lily { type_internal* create_int_type(); type_internal* create_str_type(); type_data* create_data_type(const std::string& name); + template + T* create_type(Args...as) { + auto new_type = std::make_unique(as...); + T* raw_ptr = new_type.get(); + types.push_back(std::move(new_type)); + return raw_ptr; + } - type* require_type(const std::string& name); + type* require_type(const std::string& name) const; }; }