Seemingly get basic type checking working.

This commit is contained in:
Danila Fedorin 2019-06-09 19:51:53 -07:00
parent 1e77622589
commit 711c78e0f6
12 changed files with 249 additions and 5 deletions

View File

@ -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)

73
src/ast.cpp Normal file
View File

@ -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<type_env> env) {
std::cout << "checking number" << std::endl;
return mgr.require_type("Int");
}
type* ast_var::check(type_manager& mgr, std::shared_ptr<type_env> env) {
std::cout << "checking variable" << std::endl;
return env->get_identifier_type(name);
}
type* ast_app::check(type_manager& mgr, std::shared_ptr<type_env> 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<type_func>(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<type_parameter>();
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<type_env> 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<type_env> 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<type_env> env) {
if(env->identifier_exists(name))
throw error("invalid redefinition of variable.");
type* variable_type = mgr.create_type<type_parameter>();
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<type_env> env) {
throw error("unimplemented");
}
}

View File

@ -2,10 +2,15 @@
#include <string>
#include <memory>
#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<type_env> env) = 0;
};
typedef std::unique_ptr<ast> 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<type_env> 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<type_env> 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<type_env> 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<type_env> 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<type_env> 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<type_env> env);
};
struct ast_case : ast {
@ -75,5 +86,6 @@ namespace lily {
std::vector<branch> branches;
~ast_case() = default;
type* check(type_manager& mgr, std::shared_ptr<type_env> env);
};
}

View File

@ -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;
}

View File

@ -3,6 +3,7 @@ extern "C" {
}
#include "parser.hpp"
#include "pattern.hpp"
#include "type_checker.hpp"
#include <memory>
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<std::string, std::shared_ptr<type_env>> function_envs;
auto base_env = std::make_shared<type_env>();
// First step is to collect all function types.
for(auto& pair : functions) {
// Create a local function environment.
auto function_env = std::make_shared<type_env>();
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_parameter>();
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<type_parameter>();
function_env->set_type(str, variable_param);
current_type =
type_mgr.create_type<type_func>(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]);
}
}
}

View File

@ -11,6 +11,8 @@ namespace lily {
struct program {
type_manager type_mgr;
std::map<std::string, function> functions;
void check();
};
typedef std::unique_ptr<program> program_ptr;

View File

@ -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<type_parameter*>(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<type_internal*>(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<type_data*>(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<type_func*>(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;
}
}

View File

@ -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<type*>&& 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);
};
}

32
src/type_checker.cpp Normal file
View File

@ -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> type_env::with_type(const std::string& name, type* ntype) {
auto child_env = std::make_shared<type_env>();
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;
}
}

22
src/type_checker.hpp Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include <map>
#include "type.hpp"
#include "ast.hpp"
namespace lily {
class type_env {
private:
type_env* parent = nullptr;
std::map<std::string, type*> 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<type_env> with_type(const std::string& name, type* ntype);
void set_type(const std::string& name, type* ntype);
void set_parent(type_env* new_parent);
};
}

View File

@ -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;
}
}

View File

@ -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 <typename T, typename ... Args>
T* create_type(Args...as) {
auto new_type = std::make_unique<T>(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;
};
}