Seemingly get basic type checking working.
This commit is contained in:
parent
1e77622589
commit
711c78e0f6
|
@ -2,5 +2,5 @@ cmake_minimum_required(VERSION 3.0)
|
||||||
project(lily)
|
project(lily)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 14)
|
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)
|
target_include_directories(lily PUBLIC src)
|
||||||
|
|
73
src/ast.cpp
Normal file
73
src/ast.cpp
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
12
src/ast.hpp
12
src/ast.hpp
|
@ -2,10 +2,15 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "pattern.hpp"
|
#include "pattern.hpp"
|
||||||
|
#include "type.hpp"
|
||||||
|
#include "type_manager.hpp"
|
||||||
|
|
||||||
namespace lily {
|
namespace lily {
|
||||||
|
class type_env;
|
||||||
|
|
||||||
struct ast {
|
struct ast {
|
||||||
virtual ~ast() = default;
|
virtual ~ast() = default;
|
||||||
|
virtual type* check(type_manager& mgr, std::shared_ptr<type_env> env) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::unique_ptr<ast> ast_ptr;
|
typedef std::unique_ptr<ast> ast_ptr;
|
||||||
|
@ -15,6 +20,7 @@ namespace lily {
|
||||||
|
|
||||||
ast_num(int i) : num(i) {}
|
ast_num(int i) : num(i) {}
|
||||||
~ast_num() = default;
|
~ast_num() = default;
|
||||||
|
type* check(type_manager& mgr, std::shared_ptr<type_env> env);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ast_var : ast {
|
struct ast_var : ast {
|
||||||
|
@ -23,6 +29,7 @@ namespace lily {
|
||||||
ast_var(std::string n) :
|
ast_var(std::string n) :
|
||||||
name(std::move(n)) {}
|
name(std::move(n)) {}
|
||||||
~ast_var() = default;
|
~ast_var() = default;
|
||||||
|
type* check(type_manager& mgr, std::shared_ptr<type_env> env);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ast_app : ast {
|
struct ast_app : ast {
|
||||||
|
@ -32,6 +39,7 @@ namespace lily {
|
||||||
ast_app(ast_ptr l, ast_ptr r) :
|
ast_app(ast_ptr l, ast_ptr r) :
|
||||||
left(std::move(l)), right(std::move(r)) {}
|
left(std::move(l)), right(std::move(r)) {}
|
||||||
~ast_app() = default;
|
~ast_app() = default;
|
||||||
|
type* check(type_manager& mgr, std::shared_ptr<type_env> env);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ast_op : ast {
|
struct ast_op : ast {
|
||||||
|
@ -47,6 +55,7 @@ namespace lily {
|
||||||
ast_op(enum op o, ast_ptr l, ast_ptr r) :
|
ast_op(enum op o, ast_ptr l, ast_ptr r) :
|
||||||
op(o), left(std::move(l)), right(std::move(r)) {}
|
op(o), left(std::move(l)), right(std::move(r)) {}
|
||||||
~ast_op() = default;
|
~ast_op() = default;
|
||||||
|
type* check(type_manager& mgr, std::shared_ptr<type_env> env);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ast_let : ast {
|
struct ast_let : ast {
|
||||||
|
@ -57,12 +66,14 @@ namespace lily {
|
||||||
ast_let(std::string n, ast_ptr e, ast_ptr i) :
|
ast_let(std::string n, ast_ptr e, ast_ptr i) :
|
||||||
name(std::move(n)), expr(std::move(e)), in(std::move(i)) {}
|
name(std::move(n)), expr(std::move(e)), in(std::move(i)) {}
|
||||||
~ast_let() = default;
|
~ast_let() = default;
|
||||||
|
type* check(type_manager& mgr, std::shared_ptr<type_env> env);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ast_letrec : ast_let {
|
struct ast_letrec : ast_let {
|
||||||
ast_letrec(std::string n, ast_ptr e, ast_ptr i) :
|
ast_letrec(std::string n, ast_ptr e, ast_ptr i) :
|
||||||
ast_let(std::move(n), std::move(e), std::move(i)) {}
|
ast_let(std::move(n), std::move(e), std::move(i)) {}
|
||||||
~ast_letrec() = default;
|
~ast_letrec() = default;
|
||||||
|
type* check(type_manager& mgr, std::shared_ptr<type_env> env);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ast_case : ast {
|
struct ast_case : ast {
|
||||||
|
@ -75,5 +86,6 @@ namespace lily {
|
||||||
std::vector<branch> branches;
|
std::vector<branch> branches;
|
||||||
|
|
||||||
~ast_case() = default;
|
~ast_case() = default;
|
||||||
|
type* check(type_manager& mgr, std::shared_ptr<type_env> env);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ int main() {
|
||||||
"data Bool = { True, False }\n"
|
"data Bool = { True, False }\n"
|
||||||
"data Color = { Red, Black }\n"
|
"data Color = { Red, Black }\n"
|
||||||
"data IntList = { Nil, Cons(Int, IntList) }\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) {
|
} catch(lily::error& e) {
|
||||||
std::cout << e.message << std::endl;
|
std::cout << e.message << std::endl;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ extern "C" {
|
||||||
}
|
}
|
||||||
#include "parser.hpp"
|
#include "parser.hpp"
|
||||||
#include "pattern.hpp"
|
#include "pattern.hpp"
|
||||||
|
#include "type_checker.hpp"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace lily {
|
namespace lily {
|
||||||
|
@ -238,7 +239,47 @@ namespace lily {
|
||||||
}
|
}
|
||||||
|
|
||||||
program_ptr prog = build_program(into, s.c_str());
|
program_ptr prog = build_program(into, s.c_str());
|
||||||
|
prog->check();
|
||||||
pgs_free_tree(into);
|
pgs_free_tree(into);
|
||||||
return prog;
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@ namespace lily {
|
||||||
struct program {
|
struct program {
|
||||||
type_manager type_mgr;
|
type_manager type_mgr;
|
||||||
std::map<std::string, function> functions;
|
std::map<std::string, function> functions;
|
||||||
|
|
||||||
|
void check();
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::unique_ptr<program> program_ptr;
|
typedef std::unique_ptr<program> program_ptr;
|
||||||
|
|
46
src/type.cpp
46
src/type.cpp
|
@ -1,4 +1,5 @@
|
||||||
#include "type.hpp"
|
#include "type.hpp"
|
||||||
|
#include "error.hpp"
|
||||||
|
|
||||||
namespace lily {
|
namespace lily {
|
||||||
type_data::constructor* type_data::create_constructor(const std::string& name,
|
type_data::constructor* type_data::create_constructor(const std::string& name,
|
||||||
|
@ -12,4 +13,49 @@ namespace lily {
|
||||||
constructors.push_back(std::move(new_constructor));
|
constructors.push_back(std::move(new_constructor));
|
||||||
return raw_ptr;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,19 @@ namespace lily {
|
||||||
|
|
||||||
struct type {
|
struct type {
|
||||||
virtual ~type() = default;
|
virtual ~type() = default;
|
||||||
|
virtual bool unify_with(type* other) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct type_internal : type {
|
struct type_internal : type {
|
||||||
int type_id;
|
int type_id;
|
||||||
|
|
||||||
type_internal(int id) : type_id(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 {
|
struct type_data : type {
|
||||||
|
@ -31,6 +38,7 @@ namespace lily {
|
||||||
|
|
||||||
type_data(int id) : type_id(id) {}
|
type_data(int id) : type_id(id) {}
|
||||||
constructor* create_constructor(const std::string& name, std::vector<type*>&& params);
|
constructor* create_constructor(const std::string& name, std::vector<type*>&& params);
|
||||||
|
bool unify_with(type* other);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct type_func : type {
|
struct type_func : type {
|
||||||
|
@ -39,6 +47,7 @@ namespace lily {
|
||||||
|
|
||||||
type_func(type* l, type* r) :
|
type_func(type* l, type* r) :
|
||||||
left(l), right(r) {}
|
left(l), right(r) {}
|
||||||
|
bool unify_with(type* other);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
32
src/type_checker.cpp
Normal file
32
src/type_checker.cpp
Normal 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
22
src/type_checker.hpp
Normal 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);
|
||||||
|
};
|
||||||
|
}
|
|
@ -34,8 +34,8 @@ namespace lily {
|
||||||
return raw_ptr;
|
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");
|
if(!type_names.count(name)) throw error("invalid type name");
|
||||||
return type_names[name];
|
return type_names.find(name)->second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,14 @@ namespace lily {
|
||||||
type_internal* create_int_type();
|
type_internal* create_int_type();
|
||||||
type_internal* create_str_type();
|
type_internal* create_str_type();
|
||||||
type_data* create_data_type(const std::string& name);
|
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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user