Compare commits
35 Commits
1905601aaa
...
table-of-c
| Author | SHA1 | Date | |
|---|---|---|---|
| e7d56dd4bd | |||
| a4fedb276d | |||
| 277c0a2ce6 | |||
| ef3c61e9e6 | |||
| 1908126607 | |||
| 2d77f8489f | |||
| 0371651fdd | |||
| 01734d24f7 | |||
| 71fc0546e0 | |||
| 871a745702 | |||
| 3f0df8ae0d | |||
| 1746011c16 | |||
| 7c4cfbf3d4 | |||
| 8524e098a8 | |||
| 971f58da9b | |||
| c496be1031 | |||
| 21851e3a9c | |||
| 600d5b91ea | |||
| 09b90c3bbc | |||
| f6ca13d6dc | |||
| 9c4d7c514f | |||
| ad1946e9fb | |||
| 68910458e8 | |||
| 240e87eca4 | |||
| 6b5f7e25b7 | |||
| e7229e644f | |||
| 08c8aca144 | |||
| 7f8dae74ac | |||
| 08503116ff | |||
| a1d679a59d | |||
| 4586bd0188 | |||
| a97b50f497 | |||
| c84ff11d0d | |||
| e966e74487 | |||
| 3865abfb4d |
@@ -32,6 +32,7 @@ add_executable(compiler
|
||||
binop.cpp binop.hpp
|
||||
instruction.cpp instruction.hpp
|
||||
graph.cpp graph.hpp
|
||||
global_scope.cpp global_scope.hpp
|
||||
${BISON_parser_OUTPUTS}
|
||||
${FLEX_scanner_OUTPUTS}
|
||||
main.cpp
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "binop.hpp"
|
||||
#include "error.hpp"
|
||||
#include "type_env.hpp"
|
||||
#include "env.hpp"
|
||||
|
||||
static void print_indent(int n, std::ostream& to) {
|
||||
while(n--) to << " ";
|
||||
@@ -13,14 +14,19 @@ void ast_int::print(int indent, std::ostream& to) const {
|
||||
to << "INT: " << value << std::endl;
|
||||
}
|
||||
|
||||
void ast_int::find_free(type_mgr& mgr, type_env_ptr& env, std::set<std::string>& into) {
|
||||
this->env = env;
|
||||
void ast_int::find_free(std::set<std::string>& into) {
|
||||
|
||||
}
|
||||
|
||||
type_ptr ast_int::typecheck(type_mgr& mgr) {
|
||||
type_ptr ast_int::typecheck(type_mgr& mgr, type_env_ptr& env) {
|
||||
this->env = env;
|
||||
return type_ptr(new type_app(env->lookup_type("Int")));
|
||||
}
|
||||
|
||||
void ast_int::translate(global_scope& scope) {
|
||||
|
||||
}
|
||||
|
||||
void ast_int::compile(const env_ptr& env, std::vector<instruction_ptr>& into) const {
|
||||
into.push_back(instruction_ptr(new instruction_pushint(value)));
|
||||
}
|
||||
@@ -30,20 +36,25 @@ void ast_lid::print(int indent, std::ostream& to) const {
|
||||
to << "LID: " << id << std::endl;
|
||||
}
|
||||
|
||||
void ast_lid::find_free(type_mgr& mgr, type_env_ptr& env, std::set<std::string>& into) {
|
||||
this->env = env;
|
||||
if(env->lookup(id) == nullptr) into.insert(id);
|
||||
void ast_lid::find_free(std::set<std::string>& into) {
|
||||
into.insert(id);
|
||||
}
|
||||
|
||||
type_ptr ast_lid::typecheck(type_mgr& mgr) {
|
||||
type_ptr ast_lid::typecheck(type_mgr& mgr, type_env_ptr& env) {
|
||||
this->env = env;
|
||||
return env->lookup(id)->instantiate(mgr);
|
||||
}
|
||||
|
||||
void ast_lid::translate(global_scope& scope) {
|
||||
|
||||
}
|
||||
|
||||
void ast_lid::compile(const env_ptr& env, std::vector<instruction_ptr>& into) const {
|
||||
auto mangled_name = this->env->get_mangled_name(id);
|
||||
into.push_back(instruction_ptr(
|
||||
env->has_variable(id) ?
|
||||
(instruction*) new instruction_push(env->get_offset(id)) :
|
||||
(instruction*) new instruction_pushglobal(id)));
|
||||
(env->has_variable(mangled_name) && !this->env->is_global(id)) ?
|
||||
(instruction*) new instruction_push(env->get_offset(mangled_name)) :
|
||||
(instruction*) new instruction_pushglobal(mangled_name)));
|
||||
}
|
||||
|
||||
void ast_uid::print(int indent, std::ostream& to) const {
|
||||
@@ -51,16 +62,22 @@ void ast_uid::print(int indent, std::ostream& to) const {
|
||||
to << "UID: " << id << std::endl;
|
||||
}
|
||||
|
||||
void ast_uid::find_free(type_mgr& mgr, type_env_ptr& env, std::set<std::string>& into) {
|
||||
this->env = env;
|
||||
void ast_uid::find_free(std::set<std::string>& into) {
|
||||
|
||||
}
|
||||
|
||||
type_ptr ast_uid::typecheck(type_mgr& mgr) {
|
||||
type_ptr ast_uid::typecheck(type_mgr& mgr, type_env_ptr& env) {
|
||||
this->env = env;
|
||||
return env->lookup(id)->instantiate(mgr);
|
||||
}
|
||||
|
||||
void ast_uid::translate(global_scope& scope) {
|
||||
|
||||
}
|
||||
|
||||
void ast_uid::compile(const env_ptr& env, std::vector<instruction_ptr>& into) const {
|
||||
into.push_back(instruction_ptr(new instruction_pushglobal(id)));
|
||||
into.push_back(instruction_ptr(
|
||||
new instruction_pushglobal(this->env->get_mangled_name(id))));
|
||||
}
|
||||
|
||||
void ast_binop::print(int indent, std::ostream& to) const {
|
||||
@@ -70,15 +87,15 @@ void ast_binop::print(int indent, std::ostream& to) const {
|
||||
right->print(indent + 1, to);
|
||||
}
|
||||
|
||||
void ast_binop::find_free(type_mgr& mgr, type_env_ptr& env, std::set<std::string>& into) {
|
||||
this->env = env;
|
||||
left->find_free(mgr, env, into);
|
||||
right->find_free(mgr, env, into);
|
||||
void ast_binop::find_free(std::set<std::string>& into) {
|
||||
left->find_free(into);
|
||||
right->find_free(into);
|
||||
}
|
||||
|
||||
type_ptr ast_binop::typecheck(type_mgr& mgr) {
|
||||
type_ptr ltype = left->typecheck(mgr);
|
||||
type_ptr rtype = right->typecheck(mgr);
|
||||
type_ptr ast_binop::typecheck(type_mgr& mgr, type_env_ptr& env) {
|
||||
this->env = env;
|
||||
type_ptr ltype = left->typecheck(mgr, env);
|
||||
type_ptr rtype = right->typecheck(mgr, env);
|
||||
type_ptr ftype = env->lookup(op_name(op))->instantiate(mgr);
|
||||
if(!ftype) throw type_error(std::string("unknown binary operator ") + op_name(op));
|
||||
|
||||
@@ -90,6 +107,11 @@ type_ptr ast_binop::typecheck(type_mgr& mgr) {
|
||||
return return_type;
|
||||
}
|
||||
|
||||
void ast_binop::translate(global_scope& scope) {
|
||||
left->translate(scope);
|
||||
right->translate(scope);
|
||||
}
|
||||
|
||||
void ast_binop::compile(const env_ptr& env, std::vector<instruction_ptr>& into) const {
|
||||
right->compile(env, into);
|
||||
left->compile(env_ptr(new env_offset(1, env)), into);
|
||||
@@ -106,15 +128,15 @@ void ast_app::print(int indent, std::ostream& to) const {
|
||||
right->print(indent + 1, to);
|
||||
}
|
||||
|
||||
void ast_app::find_free(type_mgr& mgr, type_env_ptr& env, std::set<std::string>& into) {
|
||||
this->env = env;
|
||||
left->find_free(mgr, env, into);
|
||||
right->find_free(mgr, env, into);
|
||||
void ast_app::find_free(std::set<std::string>& into) {
|
||||
left->find_free(into);
|
||||
right->find_free(into);
|
||||
}
|
||||
|
||||
type_ptr ast_app::typecheck(type_mgr& mgr) {
|
||||
type_ptr ltype = left->typecheck(mgr);
|
||||
type_ptr rtype = right->typecheck(mgr);
|
||||
type_ptr ast_app::typecheck(type_mgr& mgr, type_env_ptr& env) {
|
||||
this->env = env;
|
||||
type_ptr ltype = left->typecheck(mgr, env);
|
||||
type_ptr rtype = right->typecheck(mgr, env);
|
||||
|
||||
type_ptr return_type = mgr.new_type();
|
||||
type_ptr arrow = type_ptr(new type_arr(rtype, return_type));
|
||||
@@ -122,6 +144,11 @@ type_ptr ast_app::typecheck(type_mgr& mgr) {
|
||||
return return_type;
|
||||
}
|
||||
|
||||
void ast_app::translate(global_scope& scope) {
|
||||
left->translate(scope);
|
||||
right->translate(scope);
|
||||
}
|
||||
|
||||
void ast_app::compile(const env_ptr& env, std::vector<instruction_ptr>& into) const {
|
||||
right->compile(env, into);
|
||||
left->compile(env_ptr(new env_offset(1, env)), into);
|
||||
@@ -139,24 +166,30 @@ void ast_case::print(int indent, std::ostream& to) const {
|
||||
}
|
||||
}
|
||||
|
||||
void ast_case::find_free(type_mgr& mgr, type_env_ptr& env, std::set<std::string>& into) {
|
||||
this->env = env;
|
||||
of->find_free(mgr, env, into);
|
||||
void ast_case::find_free(std::set<std::string>& into) {
|
||||
of->find_free(into);
|
||||
for(auto& branch : branches) {
|
||||
type_env_ptr new_env = type_scope(env);
|
||||
branch->pat->insert_bindings(mgr, new_env);
|
||||
branch->expr->find_free(mgr, new_env, into);
|
||||
std::set<std::string> free_in_branch;
|
||||
std::set<std::string> pattern_variables;
|
||||
branch->pat->find_variables(pattern_variables);
|
||||
branch->expr->find_free(free_in_branch);
|
||||
for(auto& free : free_in_branch) {
|
||||
if(pattern_variables.find(free) == pattern_variables.end())
|
||||
into.insert(free);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type_ptr ast_case::typecheck(type_mgr& mgr) {
|
||||
type_ptr ast_case::typecheck(type_mgr& mgr, type_env_ptr& env) {
|
||||
this->env = env;
|
||||
type_var* var;
|
||||
type_ptr case_type = mgr.resolve(of->typecheck(mgr), var);
|
||||
type_ptr case_type = mgr.resolve(of->typecheck(mgr, env), var);
|
||||
type_ptr branch_type = mgr.new_type();
|
||||
|
||||
for(auto& branch : branches) {
|
||||
branch->pat->typecheck(case_type, mgr, branch->expr->env);
|
||||
type_ptr curr_branch_type = branch->expr->typecheck(mgr);
|
||||
type_env_ptr new_env = type_scope(env);
|
||||
branch->pat->typecheck(case_type, mgr, new_env);
|
||||
type_ptr curr_branch_type = branch->expr->typecheck(mgr, new_env);
|
||||
mgr.unify(branch_type, curr_branch_type);
|
||||
}
|
||||
|
||||
@@ -170,6 +203,13 @@ type_ptr ast_case::typecheck(type_mgr& mgr) {
|
||||
return branch_type;
|
||||
}
|
||||
|
||||
void ast_case::translate(global_scope& scope) {
|
||||
of->translate(scope);
|
||||
for(auto& branch : branches) {
|
||||
branch->expr->translate(scope);
|
||||
}
|
||||
}
|
||||
|
||||
void ast_case::compile(const env_ptr& env, std::vector<instruction_ptr>& into) const {
|
||||
type_app* app_type = dynamic_cast<type_app*>(input_type.get());
|
||||
type_data* type = dynamic_cast<type_data*>(app_type->constructor.get());
|
||||
@@ -199,7 +239,7 @@ void ast_case::compile(const env_ptr& env, std::vector<instruction_ptr>& into) c
|
||||
} else if((cpat = dynamic_cast<pattern_constr*>(branch->pat.get()))) {
|
||||
env_ptr new_env = env;
|
||||
for(auto it = cpat->params.rbegin(); it != cpat->params.rend(); it++) {
|
||||
new_env = env_ptr(new env_var(*it, new_env));
|
||||
new_env = env_ptr(new env_var(branch->expr->env->get_mangled_name(*it), new_env));
|
||||
}
|
||||
|
||||
branch_instructions.push_back(instruction_ptr(new instruction_split(
|
||||
@@ -226,16 +266,145 @@ void ast_case::compile(const env_ptr& env, std::vector<instruction_ptr>& into) c
|
||||
}
|
||||
}
|
||||
|
||||
void ast_let::print(int indent, std::ostream& to) const {
|
||||
print_indent(indent, to);
|
||||
to << "LET: " << std::endl;
|
||||
in->print(indent + 1, to);
|
||||
}
|
||||
|
||||
void ast_let::find_free(std::set<std::string>& into) {
|
||||
definitions.find_free(into);
|
||||
std::set<std::string> all_free;
|
||||
in->find_free(all_free);
|
||||
for(auto& free_var : all_free) {
|
||||
if(definitions.defs_defn.find(free_var) == definitions.defs_defn.end())
|
||||
into.insert(free_var);
|
||||
}
|
||||
}
|
||||
|
||||
type_ptr ast_let::typecheck(type_mgr& mgr, type_env_ptr& env) {
|
||||
this->env = env;
|
||||
definitions.typecheck(mgr, env);
|
||||
return in->typecheck(mgr, definitions.env);
|
||||
}
|
||||
|
||||
void ast_let::translate(global_scope& scope) {
|
||||
for(auto& def : definitions.defs_data) {
|
||||
def.second->into_globals(scope);
|
||||
}
|
||||
for(auto& def : definitions.defs_defn) {
|
||||
size_t original_params = def.second->params.size();
|
||||
std::string original_name = def.second->name;
|
||||
auto& global_definition = def.second->into_global(scope);
|
||||
size_t captured = global_definition.params.size() - original_params;
|
||||
|
||||
type_env_ptr mangled_env = type_scope(env);
|
||||
mangled_env->bind(def.first, env->lookup(def.first), visibility::global);
|
||||
mangled_env->set_mangled_name(def.first, global_definition.name);
|
||||
|
||||
ast_ptr global_app(new ast_lid(original_name));
|
||||
global_app->env = mangled_env;
|
||||
for(auto& param : global_definition.params) {
|
||||
if(!(captured--)) break;
|
||||
ast_ptr new_arg(new ast_lid(param));
|
||||
new_arg->env = env;
|
||||
global_app = ast_ptr(new ast_app(std::move(global_app), std::move(new_arg)));
|
||||
global_app->env = env;
|
||||
}
|
||||
translated_definitions.push_back({ def.first, std::move(global_app) });
|
||||
}
|
||||
in->translate(scope);
|
||||
}
|
||||
|
||||
void ast_let::compile(const env_ptr& env, std::vector<instruction_ptr>& into) const {
|
||||
into.push_back(instruction_ptr(new instruction_alloc(translated_definitions.size())));
|
||||
env_ptr new_env = env;
|
||||
for(auto& def : translated_definitions) {
|
||||
new_env = env_ptr(new env_var(definitions.env->get_mangled_name(def.first), std::move(new_env)));
|
||||
}
|
||||
int offset = translated_definitions.size() - 1;
|
||||
for(auto& def : translated_definitions) {
|
||||
def.second->compile(new_env, into);
|
||||
into.push_back(instruction_ptr(new instruction_update(offset--)));
|
||||
}
|
||||
in->compile(new_env, into);
|
||||
into.push_back(instruction_ptr(new instruction_slide(translated_definitions.size())));
|
||||
}
|
||||
|
||||
void ast_lambda::print(int indent, std::ostream& to) const {
|
||||
print_indent(indent, to);
|
||||
to << "LAMBDA";
|
||||
for(auto& param : params) {
|
||||
to << " " << param;
|
||||
}
|
||||
to << std::endl;
|
||||
body->print(indent+1, to);
|
||||
}
|
||||
|
||||
void ast_lambda::find_free(std::set<std::string>& into) {
|
||||
body->find_free(free_variables);
|
||||
for(auto& param : params) {
|
||||
free_variables.erase(param);
|
||||
}
|
||||
into.insert(free_variables.begin(), free_variables.end());
|
||||
}
|
||||
|
||||
type_ptr ast_lambda::typecheck(type_mgr& mgr, type_env_ptr& env) {
|
||||
this->env = env;
|
||||
var_env = type_scope(env);
|
||||
type_ptr return_type = mgr.new_type();
|
||||
type_ptr full_type = return_type;
|
||||
|
||||
for(auto it = params.rbegin(); it != params.rend(); it++) {
|
||||
type_ptr param_type = mgr.new_type();
|
||||
var_env->bind(*it, param_type);
|
||||
full_type = type_ptr(new type_arr(std::move(param_type), full_type));
|
||||
}
|
||||
|
||||
mgr.unify(return_type, body->typecheck(mgr, var_env));
|
||||
return full_type;
|
||||
}
|
||||
|
||||
void ast_lambda::translate(global_scope& scope) {
|
||||
std::vector<std::string> function_params;
|
||||
for(auto& free_variable : free_variables) {
|
||||
if(env->is_global(free_variable)) continue;
|
||||
function_params.push_back(free_variable);
|
||||
}
|
||||
size_t captured_count = function_params.size();
|
||||
function_params.insert(function_params.end(), params.begin(), params.end());
|
||||
|
||||
auto& new_function = scope.add_function("lambda", std::move(function_params), std::move(body));
|
||||
type_env_ptr mangled_env = type_scope(env);
|
||||
mangled_env->bind("lambda", type_scheme_ptr(nullptr), visibility::global);
|
||||
mangled_env->set_mangled_name("lambda", new_function.name);
|
||||
ast_ptr new_application = ast_ptr(new ast_lid("lambda"));
|
||||
new_application->env = mangled_env;
|
||||
|
||||
for(auto& param : new_function.params) {
|
||||
if(!(captured_count--)) break;
|
||||
ast_ptr new_arg = ast_ptr(new ast_lid(param));
|
||||
new_arg->env = env;
|
||||
new_application = ast_ptr(new ast_app(std::move(new_application), std::move(new_arg)));
|
||||
new_application->env = env;
|
||||
}
|
||||
translated = std::move(new_application);
|
||||
}
|
||||
|
||||
void ast_lambda::compile(const env_ptr& env, std::vector<instruction_ptr>& into) const {
|
||||
translated->compile(env, into);
|
||||
}
|
||||
|
||||
void pattern_var::print(std::ostream& to) const {
|
||||
to << var;
|
||||
}
|
||||
|
||||
void pattern_var::insert_bindings(type_mgr& mgr, type_env_ptr& env) const {
|
||||
env->bind(var, mgr.new_type());
|
||||
void pattern_var::find_variables(std::set<std::string>& into) const {
|
||||
into.insert(var);
|
||||
}
|
||||
|
||||
void pattern_var::typecheck(type_ptr t, type_mgr& mgr, type_env_ptr& env) const {
|
||||
mgr.unify(env->lookup(var)->instantiate(mgr), t);
|
||||
env->bind(var, t);
|
||||
}
|
||||
|
||||
void pattern_constr::print(std::ostream& to) const {
|
||||
@@ -245,23 +414,22 @@ void pattern_constr::print(std::ostream& to) const {
|
||||
}
|
||||
}
|
||||
|
||||
void pattern_constr::insert_bindings(type_mgr& mgr, type_env_ptr& env) const {
|
||||
for(auto& param : params) {
|
||||
env->bind(param, mgr.new_type());
|
||||
}
|
||||
void pattern_constr::find_variables(std::set<std::string>& into) const {
|
||||
into.insert(params.begin(), params.end());
|
||||
}
|
||||
|
||||
void pattern_constr::typecheck(type_ptr t, type_mgr& mgr, type_env_ptr& env) const {
|
||||
type_ptr constructor_type = env->lookup(constr)->instantiate(mgr);
|
||||
if(!constructor_type) {
|
||||
type_scheme_ptr constructor_type_scheme = env->lookup(constr);
|
||||
if(!constructor_type_scheme) {
|
||||
throw type_error(std::string("pattern using unknown constructor ") + constr);
|
||||
}
|
||||
type_ptr constructor_type = constructor_type_scheme->instantiate(mgr);
|
||||
|
||||
for(auto& param : params) {
|
||||
type_arr* arr = dynamic_cast<type_arr*>(constructor_type.get());
|
||||
if(!arr) throw type_error("too many parameters in constructor pattern");
|
||||
|
||||
mgr.unify(env->lookup(param)->instantiate(mgr), arr->left);
|
||||
env->bind(param, arr->left);
|
||||
constructor_type = arr->right;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include "binop.hpp"
|
||||
#include "instruction.hpp"
|
||||
#include "env.hpp"
|
||||
#include "definition.hpp"
|
||||
#include "global_scope.hpp"
|
||||
|
||||
struct ast {
|
||||
type_env_ptr env;
|
||||
@@ -14,9 +16,9 @@ struct ast {
|
||||
virtual ~ast() = default;
|
||||
|
||||
virtual void print(int indent, std::ostream& to) const = 0;
|
||||
virtual void find_free(type_mgr& mgr,
|
||||
type_env_ptr& env, std::set<std::string>& into) = 0;
|
||||
virtual type_ptr typecheck(type_mgr& mgr) = 0;
|
||||
virtual void find_free(std::set<std::string>& into) = 0;
|
||||
virtual type_ptr typecheck(type_mgr& mgr, type_env_ptr& env) = 0;
|
||||
virtual void translate(global_scope& scope) = 0;
|
||||
virtual void compile(const env_ptr& env,
|
||||
std::vector<instruction_ptr>& into) const = 0;
|
||||
};
|
||||
@@ -27,7 +29,7 @@ struct pattern {
|
||||
virtual ~pattern() = default;
|
||||
|
||||
virtual void print(std::ostream& to) const = 0;
|
||||
virtual void insert_bindings(type_mgr& mgr, type_env_ptr& env) const = 0;
|
||||
virtual void find_variables(std::set<std::string>& into) const = 0;
|
||||
virtual void typecheck(type_ptr t, type_mgr& mgr, type_env_ptr& env) const = 0;
|
||||
};
|
||||
|
||||
@@ -50,8 +52,9 @@ struct ast_int : public ast {
|
||||
: value(v) {}
|
||||
|
||||
void print(int indent, std::ostream& to) const;
|
||||
void find_free(type_mgr& mgr, type_env_ptr& env, std::set<std::string>& into);
|
||||
type_ptr typecheck(type_mgr& mgr);
|
||||
void find_free(std::set<std::string>& into);
|
||||
type_ptr typecheck(type_mgr& mgr, type_env_ptr& env);
|
||||
void translate(global_scope& scope);
|
||||
void compile(const env_ptr& env, std::vector<instruction_ptr>& into) const;
|
||||
};
|
||||
|
||||
@@ -62,8 +65,9 @@ struct ast_lid : public ast {
|
||||
: id(std::move(i)) {}
|
||||
|
||||
void print(int indent, std::ostream& to) const;
|
||||
void find_free(type_mgr& mgr, type_env_ptr& env, std::set<std::string>& into);
|
||||
type_ptr typecheck(type_mgr& mgr);
|
||||
void find_free(std::set<std::string>& into);
|
||||
type_ptr typecheck(type_mgr& mgr, type_env_ptr& env);
|
||||
void translate(global_scope& scope);
|
||||
void compile(const env_ptr& env, std::vector<instruction_ptr>& into) const;
|
||||
};
|
||||
|
||||
@@ -74,8 +78,9 @@ struct ast_uid : public ast {
|
||||
: id(std::move(i)) {}
|
||||
|
||||
void print(int indent, std::ostream& to) const;
|
||||
void find_free(type_mgr& mgr, type_env_ptr& env, std::set<std::string>& into);
|
||||
type_ptr typecheck(type_mgr& mgr);
|
||||
void find_free(std::set<std::string>& into);
|
||||
type_ptr typecheck(type_mgr& mgr, type_env_ptr& env);
|
||||
void translate(global_scope& scope);
|
||||
void compile(const env_ptr& env, std::vector<instruction_ptr>& into) const;
|
||||
};
|
||||
|
||||
@@ -88,8 +93,9 @@ struct ast_binop : public ast {
|
||||
: op(o), left(std::move(l)), right(std::move(r)) {}
|
||||
|
||||
void print(int indent, std::ostream& to) const;
|
||||
void find_free(type_mgr& mgr, type_env_ptr& env, std::set<std::string>& into);
|
||||
type_ptr typecheck(type_mgr& mgr);
|
||||
void find_free(std::set<std::string>& into);
|
||||
type_ptr typecheck(type_mgr& mgr, type_env_ptr& env);
|
||||
void translate(global_scope& scope);
|
||||
void compile(const env_ptr& env, std::vector<instruction_ptr>& into) const;
|
||||
};
|
||||
|
||||
@@ -101,8 +107,9 @@ struct ast_app : public ast {
|
||||
: left(std::move(l)), right(std::move(r)) {}
|
||||
|
||||
void print(int indent, std::ostream& to) const;
|
||||
void find_free(type_mgr& mgr, type_env_ptr& env, std::set<std::string>& into);
|
||||
type_ptr typecheck(type_mgr& mgr);
|
||||
void find_free(std::set<std::string>& into);
|
||||
type_ptr typecheck(type_mgr& mgr, type_env_ptr& env);
|
||||
void translate(global_scope& scope);
|
||||
void compile(const env_ptr& env, std::vector<instruction_ptr>& into) const;
|
||||
};
|
||||
|
||||
@@ -115,8 +122,46 @@ struct ast_case : public ast {
|
||||
: of(std::move(o)), branches(std::move(b)) {}
|
||||
|
||||
void print(int indent, std::ostream& to) const;
|
||||
void find_free(type_mgr& mgr, type_env_ptr& env, std::set<std::string>& into);
|
||||
type_ptr typecheck(type_mgr& mgr);
|
||||
void find_free(std::set<std::string>& into);
|
||||
type_ptr typecheck(type_mgr& mgr, type_env_ptr& env);
|
||||
void translate(global_scope& scope);
|
||||
void compile(const env_ptr& env, std::vector<instruction_ptr>& into) const;
|
||||
};
|
||||
|
||||
struct ast_let : public ast {
|
||||
using basic_definition = std::pair<std::string, ast_ptr>;
|
||||
|
||||
definition_group definitions;
|
||||
ast_ptr in;
|
||||
|
||||
std::vector<basic_definition> translated_definitions;
|
||||
|
||||
ast_let(definition_group g, ast_ptr i)
|
||||
: definitions(std::move(g)), in(std::move(i)) {}
|
||||
|
||||
void print(int indent, std::ostream& to) const;
|
||||
void find_free(std::set<std::string>& into);
|
||||
type_ptr typecheck(type_mgr& mgr, type_env_ptr& env);
|
||||
void translate(global_scope& scope);
|
||||
void compile(const env_ptr& env, std::vector<instruction_ptr>& into) const;
|
||||
};
|
||||
|
||||
struct ast_lambda : public ast {
|
||||
std::vector<std::string> params;
|
||||
ast_ptr body;
|
||||
|
||||
type_env_ptr var_env;
|
||||
|
||||
std::set<std::string> free_variables;
|
||||
ast_ptr translated;
|
||||
|
||||
ast_lambda(std::vector<std::string> ps, ast_ptr b)
|
||||
: params(std::move(ps)), body(std::move(b)) {}
|
||||
|
||||
void print(int indent, std::ostream& to) const;
|
||||
void find_free(std::set<std::string>& into);
|
||||
type_ptr typecheck(type_mgr& mgr, type_env_ptr& env);
|
||||
void translate(global_scope& scope);
|
||||
void compile(const env_ptr& env, std::vector<instruction_ptr>& into) const;
|
||||
};
|
||||
|
||||
@@ -127,7 +172,7 @@ struct pattern_var : public pattern {
|
||||
: var(std::move(v)) {}
|
||||
|
||||
void print(std::ostream &to) const;
|
||||
void insert_bindings(type_mgr& mgr, type_env_ptr& env) const;
|
||||
void find_variables(std::set<std::string>& into) const;
|
||||
void typecheck(type_ptr t, type_mgr& mgr, type_env_ptr& env) const;
|
||||
};
|
||||
|
||||
@@ -139,6 +184,6 @@ struct pattern_constr : public pattern {
|
||||
: constr(std::move(c)), params(std::move(p)) {}
|
||||
|
||||
void print(std::ostream &to) const;
|
||||
virtual void insert_bindings(type_mgr& mgr, type_env_ptr& env) const;
|
||||
void find_variables(std::set<std::string>& into) const;
|
||||
virtual void typecheck(type_ptr t, type_mgr& mgr, type_env_ptr& env) const;
|
||||
};
|
||||
|
||||
@@ -5,13 +5,20 @@
|
||||
#include "llvm_context.hpp"
|
||||
#include "type.hpp"
|
||||
#include "type_env.hpp"
|
||||
#include "graph.hpp"
|
||||
#include <llvm/IR/DerivedTypes.h>
|
||||
#include <llvm/IR/Function.h>
|
||||
#include <llvm/IR/Type.h>
|
||||
|
||||
void definition_defn::find_free(type_mgr& mgr, type_env_ptr& env) {
|
||||
this->env = env;
|
||||
void definition_defn::find_free() {
|
||||
body->find_free(free_variables);
|
||||
for(auto& param : params) {
|
||||
free_variables.erase(param);
|
||||
}
|
||||
}
|
||||
|
||||
void definition_defn::insert_types(type_mgr& mgr, type_env_ptr& env, visibility v) {
|
||||
this->env = env;
|
||||
var_env = type_scope(env);
|
||||
return_type = mgr.new_type();
|
||||
full_type = return_type;
|
||||
@@ -21,39 +28,24 @@ void definition_defn::find_free(type_mgr& mgr, type_env_ptr& env) {
|
||||
full_type = type_ptr(new type_arr(param_type, full_type));
|
||||
var_env->bind(*it, param_type);
|
||||
}
|
||||
|
||||
body->find_free(mgr, var_env, free_variables);
|
||||
}
|
||||
|
||||
void definition_defn::insert_types(type_mgr& mgr) {
|
||||
env->bind(name, full_type);
|
||||
env->bind(name, full_type, v);
|
||||
}
|
||||
|
||||
void definition_defn::typecheck(type_mgr& mgr) {
|
||||
type_ptr body_type = body->typecheck(mgr);
|
||||
type_ptr body_type = body->typecheck(mgr, var_env);
|
||||
mgr.unify(return_type, body_type);
|
||||
}
|
||||
|
||||
void definition_defn::compile() {
|
||||
env_ptr new_env = env_ptr(new env_offset(0, nullptr));
|
||||
for(auto it = params.rbegin(); it != params.rend(); it++) {
|
||||
new_env = env_ptr(new env_var(*it, new_env));
|
||||
}
|
||||
body->compile(new_env, instructions);
|
||||
instructions.push_back(instruction_ptr(new instruction_update(params.size())));
|
||||
instructions.push_back(instruction_ptr(new instruction_pop(params.size())));
|
||||
}
|
||||
|
||||
void definition_defn::declare_llvm(llvm_context& ctx) {
|
||||
generated_function = ctx.create_custom_function(name, params.size());
|
||||
global_function& definition_defn::into_global(global_scope& scope) {
|
||||
std::vector<std::string> all_params;
|
||||
for(auto& free : free_variables) {
|
||||
if(env->is_global(free)) continue;
|
||||
all_params.push_back(free);
|
||||
}
|
||||
|
||||
void definition_defn::generate_llvm(llvm_context& ctx) {
|
||||
ctx.builder.SetInsertPoint(&generated_function->getEntryBlock());
|
||||
for(auto& instruction : instructions) {
|
||||
instruction->gen_llvm(ctx, generated_function);
|
||||
}
|
||||
ctx.builder.CreateRetVoid();
|
||||
all_params.insert(all_params.end(), params.begin(), params.end());
|
||||
body->translate(scope);
|
||||
return scope.add_function(name, std::move(all_params), std::move(body));
|
||||
}
|
||||
|
||||
void definition_data::insert_types(type_env_ptr& env) {
|
||||
@@ -91,19 +83,63 @@ void definition_data::insert_constructors() const {
|
||||
}
|
||||
}
|
||||
|
||||
void definition_data::generate_llvm(llvm_context& ctx) {
|
||||
void definition_data::into_globals(global_scope& scope) {
|
||||
for(auto& constructor : constructors) {
|
||||
auto new_function =
|
||||
ctx.create_custom_function(constructor->name, constructor->types.size());
|
||||
std::vector<instruction_ptr> instructions;
|
||||
instructions.push_back(instruction_ptr(
|
||||
new instruction_pack(constructor->tag, constructor->types.size())
|
||||
));
|
||||
instructions.push_back(instruction_ptr(new instruction_update(0)));
|
||||
ctx.builder.SetInsertPoint(&new_function->getEntryBlock());
|
||||
for (auto& instruction : instructions) {
|
||||
instruction->gen_llvm(ctx, new_function);
|
||||
}
|
||||
ctx.builder.CreateRetVoid();
|
||||
global_constructor& c = scope.add_constructor(
|
||||
constructor->name, constructor->tag, constructor->types.size());
|
||||
env->set_mangled_name(constructor->name, c.name);
|
||||
}
|
||||
}
|
||||
|
||||
void definition_group::find_free(std::set<std::string>& into) {
|
||||
for(auto& def_pair : defs_defn) {
|
||||
def_pair.second->find_free();
|
||||
for(auto& free_var : def_pair.second->free_variables) {
|
||||
if(defs_defn.find(free_var) == defs_defn.end()) {
|
||||
into.insert(free_var);
|
||||
} else {
|
||||
def_pair.second->nearby_variables.insert(free_var);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void definition_group::typecheck(type_mgr& mgr, type_env_ptr& env) {
|
||||
this->env = type_scope(env);
|
||||
|
||||
for(auto& def_data : defs_data) {
|
||||
def_data.second->insert_types(this->env);
|
||||
}
|
||||
for(auto& def_data : defs_data) {
|
||||
def_data.second->insert_constructors();
|
||||
}
|
||||
|
||||
function_graph dependency_graph;
|
||||
|
||||
for(auto& def_defn : defs_defn) {
|
||||
def_defn.second->find_free();
|
||||
dependency_graph.add_function(def_defn.second->name);
|
||||
|
||||
for(auto& dependency : def_defn.second->nearby_variables) {
|
||||
if(defs_defn.find(dependency) == defs_defn.end())
|
||||
throw 0;
|
||||
dependency_graph.add_edge(def_defn.second->name, dependency);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<group_ptr> groups = dependency_graph.compute_order();
|
||||
for(auto it = groups.rbegin(); it != groups.rend(); it++) {
|
||||
auto& group = *it;
|
||||
for(auto& def_defnn_name : group->members) {
|
||||
auto& def_defn = defs_defn.find(def_defnn_name)->second;
|
||||
def_defn->insert_types(mgr, this->env, vis);
|
||||
}
|
||||
for(auto& def_defnn_name : group->members) {
|
||||
auto& def_defn = defs_defn.find(def_defnn_name)->second;
|
||||
def_defn->typecheck(mgr);
|
||||
}
|
||||
for(auto& def_defnn_name : group->members) {
|
||||
this->env->generalize(def_defnn_name, *group, mgr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include "instruction.hpp"
|
||||
#include "llvm_context.hpp"
|
||||
#include "parsed_type.hpp"
|
||||
#include "type_env.hpp"
|
||||
#include "global_scope.hpp"
|
||||
|
||||
struct ast;
|
||||
using ast_ptr = std::unique_ptr<ast>;
|
||||
@@ -29,24 +31,20 @@ struct definition_defn {
|
||||
type_env_ptr env;
|
||||
type_env_ptr var_env;
|
||||
std::set<std::string> free_variables;
|
||||
std::set<std::string> nearby_variables;
|
||||
type_ptr full_type;
|
||||
type_ptr return_type;
|
||||
|
||||
std::vector<instruction_ptr> instructions;
|
||||
|
||||
llvm::Function* generated_function;
|
||||
|
||||
definition_defn(std::string n, std::vector<std::string> p, ast_ptr b)
|
||||
: name(std::move(n)), params(std::move(p)), body(std::move(b)) {
|
||||
|
||||
}
|
||||
|
||||
void find_free(type_mgr& mgr, type_env_ptr& env);
|
||||
void insert_types(type_mgr& mgr);
|
||||
void find_free();
|
||||
void insert_types(type_mgr& mgr, type_env_ptr& env, visibility v);
|
||||
void typecheck(type_mgr& mgr);
|
||||
void compile();
|
||||
void declare_llvm(llvm_context& ctx);
|
||||
void generate_llvm(llvm_context& ctx);
|
||||
|
||||
global_function& into_global(global_scope& scope);
|
||||
};
|
||||
|
||||
using definition_defn_ptr = std::unique_ptr<definition_defn>;
|
||||
@@ -66,7 +64,20 @@ struct definition_data {
|
||||
|
||||
void insert_types(type_env_ptr& env);
|
||||
void insert_constructors() const;
|
||||
void generate_llvm(llvm_context& ctx);
|
||||
|
||||
void into_globals(global_scope& scope);
|
||||
};
|
||||
|
||||
using definition_data_ptr = std::unique_ptr<definition_data>;
|
||||
|
||||
struct definition_group {
|
||||
std::map<std::string, definition_data_ptr> defs_data;
|
||||
std::map<std::string, definition_defn_ptr> defs_defn;
|
||||
visibility vis;
|
||||
type_env_ptr env;
|
||||
|
||||
definition_group(visibility v = visibility::local) : vis(v) {}
|
||||
|
||||
void find_free(std::set<std::string>& into);
|
||||
void typecheck(type_mgr& mgr, type_env_ptr& env);
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ struct env_var : public env {
|
||||
std::string name;
|
||||
env_ptr parent;
|
||||
|
||||
env_var(std::string& n, env_ptr p)
|
||||
env_var(std::string n, env_ptr p)
|
||||
: name(std::move(n)), parent(std::move(p)) {}
|
||||
|
||||
int get_offset(const std::string& name) const;
|
||||
|
||||
17
code/compiler/12/examples/fixpoint.txt
Normal file
17
code/compiler/12/examples/fixpoint.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
data List a = { Nil, Cons a (List a) }
|
||||
|
||||
defn fix f = { let { defn x = { f x } } in { x } }
|
||||
defn fixpointOnes fo = { Cons 1 fo }
|
||||
defn sumTwo l = {
|
||||
case l of {
|
||||
Nil -> { 0 }
|
||||
Cons x xs -> {
|
||||
x + case xs of {
|
||||
Nil -> { 0 }
|
||||
Cons y ys -> { y }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defn main = { sumTwo (fix fixpointOnes) }
|
||||
19
code/compiler/12/examples/lambda.txt
Normal file
19
code/compiler/12/examples/lambda.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
data List a = { Nil, Cons a (List a) }
|
||||
|
||||
defn sum l = {
|
||||
case l of {
|
||||
Nil -> { 0 }
|
||||
Cons x xs -> { x + sum xs}
|
||||
}
|
||||
}
|
||||
|
||||
defn map f l = {
|
||||
case l of {
|
||||
Nil -> { Nil }
|
||||
Cons x xs -> { Cons (f x) (map f xs) }
|
||||
}
|
||||
}
|
||||
|
||||
defn main = {
|
||||
sum (map \x -> { x * x } (map (\x -> { x + x }) (Cons 1 (Cons 2 (Cons 3 Nil)))))
|
||||
}
|
||||
47
code/compiler/12/examples/letin.txt
Normal file
47
code/compiler/12/examples/letin.txt
Normal file
@@ -0,0 +1,47 @@
|
||||
data Bool = { True, False }
|
||||
|
||||
data List a = { Nil, Cons a (List a) }
|
||||
|
||||
defn if c t e = {
|
||||
case c of {
|
||||
True -> { t }
|
||||
False -> { e }
|
||||
}
|
||||
}
|
||||
|
||||
defn mergeUntil l r p = {
|
||||
let {
|
||||
defn mergeLeft nl nr = {
|
||||
case nl of {
|
||||
Nil -> { Nil }
|
||||
Cons x xs -> { if (p x) (Cons x (mergeRight xs nr)) Nil }
|
||||
}
|
||||
}
|
||||
defn mergeRight nl nr = {
|
||||
case nr of {
|
||||
Nil -> { Nil }
|
||||
Cons x xs -> { if (p x) (Cons x (mergeLeft nl xs)) Nil }
|
||||
}
|
||||
}
|
||||
} in {
|
||||
mergeLeft l r
|
||||
}
|
||||
}
|
||||
|
||||
defn const x y = { x }
|
||||
|
||||
defn sum l = {
|
||||
case l of {
|
||||
Nil -> { 0 }
|
||||
Cons x xs -> { x + sum xs }
|
||||
}
|
||||
}
|
||||
|
||||
defn main = {
|
||||
let {
|
||||
defn firstList = { Cons 1 (Cons 3 (Cons 5 Nil)) }
|
||||
defn secondList = { Cons 2 (Cons 4 (Cons 6 Nil)) }
|
||||
} in {
|
||||
sum (mergeUntil firstList secondList (const True))
|
||||
}
|
||||
}
|
||||
23
code/compiler/12/examples/packed.txt
Normal file
23
code/compiler/12/examples/packed.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
data Pair a b = { Pair a b }
|
||||
|
||||
defn packer = {
|
||||
let {
|
||||
data Packed a = { Packed a }
|
||||
defn pack a = { Packed a }
|
||||
defn unpack p = {
|
||||
case p of {
|
||||
Packed a -> { a }
|
||||
}
|
||||
}
|
||||
} in {
|
||||
Pair pack unpack
|
||||
}
|
||||
}
|
||||
|
||||
defn main = {
|
||||
case packer of {
|
||||
Pair pack unpack -> {
|
||||
unpack (pack 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
83
code/compiler/12/global_scope.cpp
Normal file
83
code/compiler/12/global_scope.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#include "global_scope.hpp"
|
||||
#include "ast.hpp"
|
||||
|
||||
void global_function::compile() {
|
||||
env_ptr new_env = env_ptr(new env_offset(0, nullptr));
|
||||
for(auto it = params.rbegin(); it != params.rend(); it++) {
|
||||
new_env = env_ptr(new env_var(*it, new_env));
|
||||
}
|
||||
body->compile(new_env, instructions);
|
||||
instructions.push_back(instruction_ptr(new instruction_update(params.size())));
|
||||
instructions.push_back(instruction_ptr(new instruction_pop(params.size())));
|
||||
}
|
||||
|
||||
void global_function::declare_llvm(llvm_context& ctx) {
|
||||
generated_function = ctx.create_custom_function(name, params.size());
|
||||
}
|
||||
|
||||
void global_function::generate_llvm(llvm_context& ctx) {
|
||||
ctx.builder.SetInsertPoint(&generated_function->getEntryBlock());
|
||||
for(auto& instruction : instructions) {
|
||||
instruction->gen_llvm(ctx, generated_function);
|
||||
}
|
||||
ctx.builder.CreateRetVoid();
|
||||
}
|
||||
|
||||
void global_constructor::generate_llvm(llvm_context& ctx) {
|
||||
auto new_function =
|
||||
ctx.create_custom_function(name, arity);
|
||||
std::vector<instruction_ptr> instructions;
|
||||
instructions.push_back(instruction_ptr(new instruction_pack(tag, arity)));
|
||||
instructions.push_back(instruction_ptr(new instruction_update(0)));
|
||||
ctx.builder.SetInsertPoint(&new_function->getEntryBlock());
|
||||
for (auto& instruction : instructions) {
|
||||
instruction->gen_llvm(ctx, new_function);
|
||||
}
|
||||
ctx.builder.CreateRetVoid();
|
||||
}
|
||||
|
||||
global_function& global_scope::add_function(std::string n, std::vector<std::string> ps, ast_ptr b) {
|
||||
global_function* new_function = new global_function(mangle_name(n), std::move(ps), std::move(b));
|
||||
functions.push_back(global_function_ptr(new_function));
|
||||
return *new_function;
|
||||
}
|
||||
|
||||
global_constructor& global_scope::add_constructor(std::string n, int8_t t, size_t a) {
|
||||
global_constructor* new_constructor = new global_constructor(mangle_name(n), t, a);
|
||||
constructors.push_back(global_constructor_ptr(new_constructor));
|
||||
return *new_constructor;
|
||||
}
|
||||
|
||||
void global_scope::compile() {
|
||||
for(auto& function : functions) {
|
||||
function->compile();
|
||||
}
|
||||
}
|
||||
|
||||
void global_scope::generate_llvm(llvm_context& ctx) {
|
||||
for(auto& constructor : constructors) {
|
||||
constructor->generate_llvm(ctx);
|
||||
}
|
||||
for(auto& function : functions) {
|
||||
function->declare_llvm(ctx);
|
||||
}
|
||||
for(auto& function : functions) {
|
||||
function->generate_llvm(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
std::string global_scope::mangle_name(const std::string& n) {
|
||||
auto occurence_it = occurence_count.find(n);
|
||||
int occurence = 0;
|
||||
if(occurence_it != occurence_count.end()) {
|
||||
occurence = occurence_it->second + 1;
|
||||
}
|
||||
occurence_count[n] = occurence;
|
||||
|
||||
std::string final_name = n;
|
||||
if (occurence != 0) {
|
||||
final_name += "_";
|
||||
final_name += std::to_string(occurence);
|
||||
}
|
||||
return final_name;
|
||||
}
|
||||
55
code/compiler/12/global_scope.hpp
Normal file
55
code/compiler/12/global_scope.hpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <llvm/IR/Function.h>
|
||||
#include "instruction.hpp"
|
||||
|
||||
struct ast;
|
||||
using ast_ptr = std::unique_ptr<ast>;
|
||||
|
||||
struct global_function {
|
||||
std::string name;
|
||||
std::vector<std::string> params;
|
||||
ast_ptr body;
|
||||
|
||||
std::vector<instruction_ptr> instructions;
|
||||
llvm::Function* generated_function;
|
||||
|
||||
global_function(std::string n, std::vector<std::string> ps, ast_ptr b)
|
||||
: name(std::move(n)), params(std::move(ps)), body(std::move(b)) {}
|
||||
|
||||
void compile();
|
||||
void declare_llvm(llvm_context& ctx);
|
||||
void generate_llvm(llvm_context& ctx);
|
||||
};
|
||||
|
||||
using global_function_ptr = std::unique_ptr<global_function>;
|
||||
|
||||
struct global_constructor {
|
||||
std::string name;
|
||||
int8_t tag;
|
||||
size_t arity;
|
||||
|
||||
global_constructor(std::string n, int8_t t, size_t a)
|
||||
: name(std::move(n)), tag(t), arity(a) {}
|
||||
|
||||
void generate_llvm(llvm_context& ctx);
|
||||
};
|
||||
|
||||
using global_constructor_ptr = std::unique_ptr<global_constructor>;
|
||||
|
||||
struct global_scope {
|
||||
std::map<std::string, int> occurence_count;
|
||||
std::vector<global_function_ptr> functions;
|
||||
std::vector<global_constructor_ptr> constructors;
|
||||
|
||||
global_function& add_function(std::string n, std::vector<std::string> ps, ast_ptr b);
|
||||
global_constructor& add_constructor(std::string n, int8_t t, size_t a);
|
||||
|
||||
void compile();
|
||||
void generate_llvm(llvm_context& ctx);
|
||||
|
||||
private:
|
||||
std::string mangle_name(const std::string& n);
|
||||
};
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
using function = std::string;
|
||||
|
||||
|
||||
@@ -21,12 +21,10 @@ void yy::parser::error(const std::string& msg) {
|
||||
std::cout << "An error occured: " << msg << std::endl;
|
||||
}
|
||||
|
||||
extern std::map<std::string, definition_data_ptr> defs_data;
|
||||
extern std::map<std::string, definition_defn_ptr> defs_defn;
|
||||
extern definition_group global_defs;
|
||||
|
||||
void typecheck_program(
|
||||
const std::map<std::string, definition_data_ptr>& defs_data,
|
||||
const std::map<std::string, definition_defn_ptr>& defs_defn,
|
||||
definition_group& defs,
|
||||
type_mgr& mgr, type_env_ptr& env) {
|
||||
type_ptr int_type = type_ptr(new type_base("Int"));
|
||||
env->bind_type("Int", int_type);
|
||||
@@ -35,63 +33,32 @@ void typecheck_program(
|
||||
type_ptr binop_type = type_ptr(new type_arr(
|
||||
int_type_app,
|
||||
type_ptr(new type_arr(int_type_app, int_type_app))));
|
||||
env->bind("+", binop_type);
|
||||
env->bind("-", binop_type);
|
||||
env->bind("*", binop_type);
|
||||
env->bind("/", binop_type);
|
||||
env->bind("+", binop_type, visibility::global);
|
||||
env->bind("-", binop_type, visibility::global);
|
||||
env->bind("*", binop_type, visibility::global);
|
||||
env->bind("/", binop_type, visibility::global);
|
||||
|
||||
for(auto& def_data : defs_data) {
|
||||
def_data.second->insert_types(env);
|
||||
}
|
||||
for(auto& def_data : defs_data) {
|
||||
def_data.second->insert_constructors();
|
||||
}
|
||||
std::set<std::string> free;
|
||||
defs.find_free(free);
|
||||
defs.typecheck(mgr, env);
|
||||
|
||||
function_graph dependency_graph;
|
||||
|
||||
for(auto& def_defn : defs_defn) {
|
||||
def_defn.second->find_free(mgr, env);
|
||||
dependency_graph.add_function(def_defn.second->name);
|
||||
|
||||
for(auto& dependency : def_defn.second->free_variables) {
|
||||
if(defs_defn.find(dependency) == defs_defn.end())
|
||||
throw 0;
|
||||
dependency_graph.add_edge(def_defn.second->name, dependency);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<group_ptr> groups = dependency_graph.compute_order();
|
||||
for(auto it = groups.rbegin(); it != groups.rend(); it++) {
|
||||
auto& group = *it;
|
||||
for(auto& def_defnn_name : group->members) {
|
||||
auto& def_defn = defs_defn.find(def_defnn_name)->second;
|
||||
def_defn->insert_types(mgr);
|
||||
}
|
||||
for(auto& def_defnn_name : group->members) {
|
||||
auto& def_defn = defs_defn.find(def_defnn_name)->second;
|
||||
def_defn->typecheck(mgr);
|
||||
}
|
||||
for(auto& def_defnn_name : group->members) {
|
||||
env->generalize(def_defnn_name, mgr);
|
||||
}
|
||||
}
|
||||
|
||||
for(auto& pair : env->names) {
|
||||
for(auto& pair : defs.env->names) {
|
||||
std::cout << pair.first << ": ";
|
||||
pair.second->print(mgr, std::cout);
|
||||
pair.second.type->print(mgr, std::cout);
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void compile_program(const std::map<std::string, definition_defn_ptr>& defs_defn) {
|
||||
for(auto& def_defn : defs_defn) {
|
||||
def_defn.second->compile();
|
||||
|
||||
for(auto& instruction : def_defn.second->instructions) {
|
||||
instruction->print(0, std::cout);
|
||||
global_scope translate_program(definition_group& group) {
|
||||
global_scope scope;
|
||||
for(auto& data : group.defs_data) {
|
||||
data.second->into_globals(scope);
|
||||
}
|
||||
std::cout << std::endl;
|
||||
for(auto& defn : group.defs_defn) {
|
||||
auto& function = defn.second->into_global(scope);
|
||||
function.body->env->parent->set_mangled_name(defn.first, function.name);
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
||||
void gen_llvm_internal_op(llvm_context& ctx, binop op) {
|
||||
@@ -151,24 +118,14 @@ void output_llvm(llvm_context& ctx, const std::string& filename) {
|
||||
}
|
||||
}
|
||||
|
||||
void gen_llvm(
|
||||
const std::map<std::string, definition_data_ptr>& defs_data,
|
||||
const std::map<std::string, definition_defn_ptr>& defs_defn) {
|
||||
void gen_llvm(global_scope& scope) {
|
||||
llvm_context ctx;
|
||||
gen_llvm_internal_op(ctx, PLUS);
|
||||
gen_llvm_internal_op(ctx, MINUS);
|
||||
gen_llvm_internal_op(ctx, TIMES);
|
||||
gen_llvm_internal_op(ctx, DIVIDE);
|
||||
|
||||
for(auto& def_data : defs_data) {
|
||||
def_data.second->generate_llvm(ctx);
|
||||
}
|
||||
for(auto& def_defn : defs_defn) {
|
||||
def_defn.second->declare_llvm(ctx);
|
||||
}
|
||||
for(auto& def_defn : defs_defn) {
|
||||
def_defn.second->generate_llvm(ctx);
|
||||
}
|
||||
scope.generate_llvm(ctx);
|
||||
|
||||
ctx.module.print(llvm::outs(), nullptr);
|
||||
output_llvm(ctx, "program.o");
|
||||
@@ -180,7 +137,7 @@ int main() {
|
||||
type_env_ptr env(new type_env);
|
||||
|
||||
parser.parse();
|
||||
for(auto& def_defn : defs_defn) {
|
||||
for(auto& def_defn : global_defs.defs_defn) {
|
||||
std::cout << def_defn.second->name;
|
||||
for(auto& param : def_defn.second->params) std::cout << " " << param;
|
||||
std::cout << ":" << std::endl;
|
||||
@@ -188,9 +145,10 @@ int main() {
|
||||
def_defn.second->body->print(1, std::cout);
|
||||
}
|
||||
try {
|
||||
typecheck_program(defs_data, defs_defn, mgr, env);
|
||||
compile_program(defs_defn);
|
||||
gen_llvm(defs_data, defs_defn);
|
||||
typecheck_program(global_defs, mgr, env);
|
||||
global_scope scope = translate_program(global_defs);
|
||||
scope.compile();
|
||||
gen_llvm(scope);
|
||||
} catch(unification_error& err) {
|
||||
std::cout << "failed to unify types: " << std::endl;
|
||||
std::cout << " (1) \033[34m";
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
#include "parser.hpp"
|
||||
#include "parsed_type.hpp"
|
||||
|
||||
std::map<std::string, definition_data_ptr> defs_data;
|
||||
std::map<std::string, definition_defn_ptr> defs_defn;
|
||||
definition_group global_defs;
|
||||
|
||||
extern yy::parser::symbol_type yylex();
|
||||
|
||||
%}
|
||||
|
||||
%token BACKSLASH
|
||||
%token PLUS
|
||||
%token TIMES
|
||||
%token MINUS
|
||||
@@ -23,6 +23,8 @@ extern yy::parser::symbol_type yylex();
|
||||
%token DATA
|
||||
%token CASE
|
||||
%token OF
|
||||
%token LET
|
||||
%token IN
|
||||
%token OCURLY
|
||||
%token CCURLY
|
||||
%token OPAREN
|
||||
@@ -41,8 +43,9 @@ extern yy::parser::symbol_type yylex();
|
||||
%type <std::vector<branch_ptr>> branches
|
||||
%type <std::vector<constructor_ptr>> constructors
|
||||
%type <std::vector<parsed_type_ptr>> typeList
|
||||
%type <definition_group> definitions
|
||||
%type <parsed_type_ptr> type nonArrowType typeListElement
|
||||
%type <ast_ptr> aAdd aMul case app appBase
|
||||
%type <ast_ptr> aAdd aMul case let lambda app appBase
|
||||
%type <definition_data_ptr> data
|
||||
%type <definition_defn_ptr> defn
|
||||
%type <branch_ptr> branch
|
||||
@@ -54,17 +57,13 @@ extern yy::parser::symbol_type yylex();
|
||||
%%
|
||||
|
||||
program
|
||||
: definitions { }
|
||||
: definitions { global_defs = std::move($1); global_defs.vis = visibility::global; }
|
||||
;
|
||||
|
||||
definitions
|
||||
: definitions definition { }
|
||||
| definition { }
|
||||
;
|
||||
|
||||
definition
|
||||
: defn { auto name = $1->name; defs_defn[name] = std::move($1); }
|
||||
| data { auto name = $1->name; defs_data[name] = std::move($1); }
|
||||
: definitions defn { $$ = std::move($1); auto name = $2->name; $$.defs_defn[name] = std::move($2); }
|
||||
| definitions data { $$ = std::move($1); auto name = $2->name; $$.defs_data[name] = std::move($2); }
|
||||
| %empty { $$ = definition_group(); }
|
||||
;
|
||||
|
||||
defn
|
||||
@@ -101,6 +100,18 @@ appBase
|
||||
| UID { $$ = ast_ptr(new ast_uid(std::move($1))); }
|
||||
| OPAREN aAdd CPAREN { $$ = std::move($2); }
|
||||
| case { $$ = std::move($1); }
|
||||
| let { $$ = std::move($1); }
|
||||
| lambda { $$ = std::move($1); }
|
||||
;
|
||||
|
||||
let
|
||||
: LET OCURLY definitions CCURLY IN OCURLY aAdd CCURLY
|
||||
{ $$ = ast_ptr(new ast_let(std::move($3), std::move($7))); }
|
||||
;
|
||||
|
||||
lambda
|
||||
: BACKSLASH lowercaseParams ARROW OCURLY aAdd CCURLY
|
||||
{ $$ = ast_ptr(new ast_lambda(std::move($2), std::move($5))); }
|
||||
;
|
||||
|
||||
case
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
%%
|
||||
|
||||
[ \n]+ {}
|
||||
\\ { return yy::parser::make_BACKSLASH(); }
|
||||
\+ { return yy::parser::make_PLUS(); }
|
||||
\* { return yy::parser::make_TIMES(); }
|
||||
- { return yy::parser::make_MINUS(); }
|
||||
@@ -22,6 +23,8 @@ defn { return yy::parser::make_DEFN(); }
|
||||
data { return yy::parser::make_DATA(); }
|
||||
case { return yy::parser::make_CASE(); }
|
||||
of { return yy::parser::make_OF(); }
|
||||
let { return yy::parser::make_LET(); }
|
||||
in { return yy::parser::make_IN(); }
|
||||
\{ { return yy::parser::make_OCURLY(); }
|
||||
\} { return yy::parser::make_CCURLY(); }
|
||||
\( { return yy::parser::make_OPAREN(); }
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <vector>
|
||||
#include "error.hpp"
|
||||
|
||||
bool type::is_arrow(const type_mgr& mgr) const { return false; }
|
||||
|
||||
void type_scheme::print(const type_mgr& mgr, std::ostream& to) const {
|
||||
if(forall.size() != 0) {
|
||||
to << "forall ";
|
||||
@@ -34,15 +36,30 @@ void type_var::print(const type_mgr& mgr, std::ostream& to) const {
|
||||
}
|
||||
}
|
||||
|
||||
bool type_var::is_arrow(const type_mgr& mgr) const {
|
||||
auto it = mgr.types.find(name);
|
||||
if(it != mgr.types.end()) {
|
||||
return it->second->is_arrow(mgr);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void type_base::print(const type_mgr& mgr, std::ostream& to) const {
|
||||
to << name;
|
||||
}
|
||||
|
||||
void type_arr::print(const type_mgr& mgr, std::ostream& to) const {
|
||||
bool print_parenths = left->is_arrow(mgr);
|
||||
if(print_parenths) to << "(";
|
||||
left->print(mgr, to);
|
||||
to << " -> (";
|
||||
if(print_parenths) to << ")";
|
||||
to << " -> ";
|
||||
right->print(mgr, to);
|
||||
to << ")";
|
||||
}
|
||||
|
||||
bool type_arr::is_arrow(const type_mgr& mgr) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void type_app::print(const type_mgr& mgr, std::ostream& to) const {
|
||||
@@ -185,3 +202,18 @@ void type_mgr::find_free(const type_ptr& t, std::set<std::string>& into) const {
|
||||
for(auto& arg : app->arguments) find_free(arg, into);
|
||||
}
|
||||
}
|
||||
|
||||
void type_mgr::find_free(const type_scheme_ptr& t, std::set<std::string>& into) const {
|
||||
std::set<std::string> monotype_free;
|
||||
type_mgr limited_mgr;
|
||||
for(auto& binding : types) {
|
||||
auto existing_position = std::find(t->forall.begin(), t->forall.end(), binding.first);
|
||||
if(existing_position != t->forall.end()) continue;
|
||||
limited_mgr.types[binding.first] = binding.second;
|
||||
}
|
||||
limited_mgr.find_free(t->monotype, monotype_free);
|
||||
for(auto& not_free : t->forall) {
|
||||
monotype_free.erase(not_free);
|
||||
}
|
||||
into.insert(monotype_free.begin(), monotype_free.end());
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ struct type {
|
||||
virtual ~type() = default;
|
||||
|
||||
virtual void print(const type_mgr& mgr, std::ostream& to) const = 0;
|
||||
virtual bool is_arrow(const type_mgr& mgr) const;
|
||||
};
|
||||
|
||||
using type_ptr = std::shared_ptr<type>;
|
||||
@@ -34,6 +35,7 @@ struct type_var : public type {
|
||||
: name(std::move(n)) {}
|
||||
|
||||
void print(const type_mgr& mgr, std::ostream& to) const;
|
||||
bool is_arrow(const type_mgr& mgr) const;
|
||||
};
|
||||
|
||||
struct type_base : public type {
|
||||
@@ -65,6 +67,7 @@ struct type_arr : public type {
|
||||
: left(std::move(l)), right(std::move(r)) {}
|
||||
|
||||
void print(const type_mgr& mgr, std::ostream& to) const;
|
||||
bool is_arrow(const type_mgr& mgr) const;
|
||||
};
|
||||
|
||||
struct type_app : public type {
|
||||
@@ -92,4 +95,5 @@ struct type_mgr {
|
||||
type_ptr resolve(type_ptr t, type_var*& var) const;
|
||||
void bind(const std::string& s, type_ptr t);
|
||||
void find_free(const type_ptr& t, std::set<std::string>& into) const;
|
||||
void find_free(const type_scheme_ptr& t, std::set<std::string>& into) const;
|
||||
};
|
||||
|
||||
@@ -1,13 +1,49 @@
|
||||
#include "type_env.hpp"
|
||||
#include "type.hpp"
|
||||
|
||||
void type_env::find_free(const type_mgr& mgr, std::set<std::string>& into) const {
|
||||
if(parent != nullptr) parent->find_free(mgr, into);
|
||||
for(auto& binding : names) {
|
||||
mgr.find_free(binding.second.type, into);
|
||||
}
|
||||
}
|
||||
|
||||
void type_env::find_free_except(const type_mgr& mgr, const group& avoid,
|
||||
std::set<std::string>& into) const {
|
||||
if(parent != nullptr) parent->find_free(mgr, into);
|
||||
for(auto& binding : names) {
|
||||
if(avoid.members.find(binding.first) != avoid.members.end()) continue;
|
||||
mgr.find_free(binding.second.type, into);
|
||||
}
|
||||
}
|
||||
|
||||
type_scheme_ptr type_env::lookup(const std::string& name) const {
|
||||
auto it = names.find(name);
|
||||
if(it != names.end()) return it->second;
|
||||
if(it != names.end()) return it->second.type;
|
||||
if(parent) return parent->lookup(name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool type_env::is_global(const std::string& name) const {
|
||||
auto it = names.find(name);
|
||||
if(it != names.end()) return it->second.vis == visibility::global;
|
||||
if(parent) return parent->is_global(name);
|
||||
return false;
|
||||
}
|
||||
|
||||
void type_env::set_mangled_name(const std::string& name, const std::string& mangled) {
|
||||
auto it = names.find(name);
|
||||
if(it != names.end()) it->second.mangled_name = mangled;
|
||||
}
|
||||
|
||||
const std::string& type_env::get_mangled_name(const std::string& name) const {
|
||||
auto it = names.find(name);
|
||||
if(it != names.end())
|
||||
return (it->second.mangled_name != "") ? it->second.mangled_name : name;
|
||||
if(parent) return parent->get_mangled_name(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
type_ptr type_env::lookup_type(const std::string& name) const {
|
||||
auto it = type_names.find(name);
|
||||
if(it != type_names.end()) return it->second;
|
||||
@@ -15,12 +51,13 @@ type_ptr type_env::lookup_type(const std::string& name) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void type_env::bind(const std::string& name, type_ptr t) {
|
||||
names[name] = type_scheme_ptr(new type_scheme(t));
|
||||
void type_env::bind(const std::string& name, type_ptr t, visibility v) {
|
||||
type_scheme_ptr new_scheme(new type_scheme(std::move(t)));
|
||||
names[name] = variable_data(std::move(new_scheme), v, "");
|
||||
}
|
||||
|
||||
void type_env::bind(const std::string& name, type_scheme_ptr t) {
|
||||
names[name] = t;
|
||||
void type_env::bind(const std::string& name, type_scheme_ptr t, visibility v) {
|
||||
names[name] = variable_data(std::move(t), v, "");
|
||||
}
|
||||
|
||||
void type_env::bind_type(const std::string& type_name, type_ptr t) {
|
||||
@@ -28,15 +65,18 @@ void type_env::bind_type(const std::string& type_name, type_ptr t) {
|
||||
type_names[type_name] = t;
|
||||
}
|
||||
|
||||
void type_env::generalize(const std::string& name, type_mgr& mgr) {
|
||||
void type_env::generalize(const std::string& name, const group& grp, type_mgr& mgr) {
|
||||
auto names_it = names.find(name);
|
||||
if(names_it == names.end()) throw 0;
|
||||
if(names_it->second->forall.size() > 0) throw 0;
|
||||
if(names_it->second.type->forall.size() > 0) throw 0;
|
||||
|
||||
std::set<std::string> free_variables;
|
||||
mgr.find_free(names_it->second->monotype, free_variables);
|
||||
for(auto& free : free_variables) {
|
||||
names_it->second->forall.push_back(free);
|
||||
std::set<std::string> free_in_type;
|
||||
std::set<std::string> free_in_env;
|
||||
mgr.find_free(names_it->second.type->monotype, free_in_type);
|
||||
find_free_except(mgr, grp, free_in_env);
|
||||
for(auto& free : free_in_type) {
|
||||
if(free_in_env.find(free) != free_in_env.end()) continue;
|
||||
names_it->second.type->forall.push_back(free);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,48 @@
|
||||
#pragma once
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include "graph.hpp"
|
||||
#include "type.hpp"
|
||||
|
||||
struct type_env;
|
||||
using type_env_ptr = std::shared_ptr<type_env>;
|
||||
|
||||
enum class visibility { global,local };
|
||||
|
||||
struct type_env {
|
||||
struct variable_data {
|
||||
type_scheme_ptr type;
|
||||
visibility vis;
|
||||
std::string mangled_name;
|
||||
|
||||
variable_data()
|
||||
: variable_data(nullptr, visibility::local, "") {}
|
||||
variable_data(type_scheme_ptr t, visibility v, std::string n)
|
||||
: type(std::move(t)), vis(v), mangled_name(std::move(n)) {}
|
||||
};
|
||||
|
||||
type_env_ptr parent;
|
||||
std::map<std::string, type_scheme_ptr> names;
|
||||
std::map<std::string, variable_data> names;
|
||||
std::map<std::string, type_ptr> type_names;
|
||||
|
||||
type_env(type_env_ptr p) : parent(std::move(p)) {}
|
||||
type_env() : type_env(nullptr) {}
|
||||
|
||||
void find_free(const type_mgr& mgr, std::set<std::string>& into) const;
|
||||
void find_free_except(const type_mgr& mgr, const group& avoid,
|
||||
std::set<std::string>& into) const;
|
||||
type_scheme_ptr lookup(const std::string& name) const;
|
||||
bool is_global(const std::string& name) const;
|
||||
void set_mangled_name(const std::string& name, const std::string& mangled);
|
||||
const std::string& get_mangled_name(const std::string& name) const;
|
||||
type_ptr lookup_type(const std::string& name) const;
|
||||
void bind(const std::string& name, type_ptr t);
|
||||
void bind(const std::string& name, type_scheme_ptr t);
|
||||
void bind(const std::string& name, type_ptr t,
|
||||
visibility v = visibility::local);
|
||||
void bind(const std::string& name, type_scheme_ptr t,
|
||||
visibility v = visibility::local);
|
||||
void bind_type(const std::string& type_name, type_ptr t);
|
||||
void generalize(const std::string& name, type_mgr& mgr);
|
||||
void generalize(const std::string& name, const group& grp, type_mgr& mgr);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -5,3 +5,9 @@ theme = "vanilla"
|
||||
pygmentsCodeFences = true
|
||||
pygmentsStyle = "github"
|
||||
summaryLength = 20
|
||||
|
||||
[markup]
|
||||
[markup.tableOfContents]
|
||||
endLevel = 4
|
||||
ordered = false
|
||||
startLevel = 3
|
||||
|
||||
@@ -144,3 +144,5 @@ Here are the posts that I've written so far for this series:
|
||||
* [Garbage Collection]({{< relref "09_compiler_garbage_collection.md" >}})
|
||||
* [Polymorphism]({{< relref "10_compiler_polymorphism.md" >}})
|
||||
* [Polymorphic Data Types]({{< relref "11_compiler_polymorphic_data_types.md" >}})
|
||||
* [Let/In and Lambdas]({{< relref "12_compiler_let_in_lambda/index.md" >}})
|
||||
|
||||
|
||||
@@ -396,4 +396,5 @@ Result: 4
|
||||
|
||||
This looks good! We have added support for polymorphic data types to our compiler.
|
||||
We are now free to move on to `let/in` expressions, __lambda functions__, and __Input/Output__,
|
||||
as promised! I'll see you then!
|
||||
as promised, starting with [part 12]({{< relref "12_compiler_let_in_lambda/index.md" >}}) - `let/in`
|
||||
and lambdas!
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
---
|
||||
title: Compiling a Functional Language Using C++, Part 12 - Let/In and Lambdas
|
||||
date: 2020-04-20T20:15:16-07:00
|
||||
date: 2020-06-21T00:50:07-07:00
|
||||
tags: ["C and C++", "Functional Languages", "Compilers"]
|
||||
description: "In this post, we extend our language with let/in expressions and lambda functions."
|
||||
draft: true
|
||||
---
|
||||
|
||||
Now that our language's type system is more fleshed out and pleasant to use, it's time to shift our focus to the ergonomics of the language itself. I've been mentioning `let/in` expressions and __lambda expressions__ for a while now. The former will let us create names for expressions that are limited to a certain scope (without having to create global variable bindings), while the latter will allow us to create functions without giving them any name at all.
|
||||
Now that our language's type system is more fleshed out and pleasant to use, it's time to shift our focus to the ergonomics of the language itself. I've been mentioning `let/in` and __lambda__ expressions for a while now. The former will let us create names for expressions that are limited to a certain scope (without having to create global variable bindings), while the latter will allow us to create functions without giving them any name at all.
|
||||
|
||||
Let's take a look at `let/in` expressions first, to make sure we're all on the same page about what it is we're trying to implement. Let's start with some rather basic examples, and then move on to more complex examples. The most basic use of a `let/in` expression is, in Haskell:
|
||||
Let's take a look at `let/in` expressions first, to make sure we're all on the same page about what it is we're trying to implement. Let's start with some rather basic examples, and then move on to more complex ones. A very basic use of a `let/in` expression is, in Haskell:
|
||||
|
||||
```Haskell
|
||||
let x = 5 in x + x
|
||||
@@ -93,7 +92,7 @@ addSingle6 x = 6 + x
|
||||
-- ... and so on ...
|
||||
```
|
||||
|
||||
But now, we end up creating several functions with almost identical bodies, with the exception of the free variables themselves. Wouldn't it be better to perform the well-known strategy of reducing code duplication by factoring out parameters, and leaving only instance of the repeated code? We would end up with:
|
||||
But now, we end up creating several functions with almost identical bodies, with the exception of the free variables themselves. Wouldn't it be better to perform the well-known strategy of reducing code duplication by factoring out parameters, and leaving only one instance of the repeated code? We would end up with:
|
||||
|
||||
```Haskell {linenos=table}
|
||||
addToAll n xs = map (addSingle n) xs
|
||||
@@ -106,4 +105,884 @@ Wait a moment, didn't we just talk about nested polymorphic definitions, and how
|
||||
This is true, but why should we perform transformations on a malformed program? Typechecking before pulling functions to the global scope will help us save the work, and breaking down one dependency-searching problem (which is \(O(n^3)\) thanks to Warshall's) into smaller, independent problems may even lead to better performance. Furthermore, typechecking before program transformations will help us come up with more helpful error messages.
|
||||
{{< /sidenote >}} and can be transformed into a sequence of instructions just like any other global function. It has been pulled from its `where` (which, by the way, is pretty much equivalent to a `let/in`) to the top level.
|
||||
|
||||
Now, see how `addSingle` became `(addSingle n)`? If we chose to rewrite the
|
||||
program this way, we'd have to find-and-replace every instance of `addSingle`
|
||||
in the function body, which would be tedious and require us to keep
|
||||
track of shadowed variables and the like. Also, what if we used a local
|
||||
definition twice in the original piece of code? How about something like this:
|
||||
|
||||
```Haskell {linenos=table}
|
||||
fourthPower x = square * square
|
||||
where
|
||||
square = x * x
|
||||
```
|
||||
|
||||
Applying the strategy we saw above, we get:
|
||||
|
||||
```Haskell {linenos=table}
|
||||
fourthPower x = (square x) * (square x)
|
||||
square x = x * x
|
||||
```
|
||||
|
||||
This is valid, except that in our evaluation model, the two instances
|
||||
of `(square x)` will be built independently of one another, and thus,
|
||||
will not be shared. This, in turn, will mean that `square` will be called
|
||||
twice, which is not what we would expect from looking at the original program.
|
||||
This isn't good. Instead, why don't we keep the `where`, but modify it
|
||||
as follows:
|
||||
|
||||
```Haskell {linenos=table}
|
||||
fourthPower x = square * square
|
||||
where square = square' x
|
||||
square' x = x * x
|
||||
```
|
||||
|
||||
This time, assuming we can properly implement `where`, the call to
|
||||
`square' x` should only occur once. Though I've been using `where`,
|
||||
which leads to less clutter in Haskell code, the exact same approach applies
|
||||
to `let/in`, and that's what we'll be using in our language.
|
||||
|
||||
This technique of replacing captured variables with arguments, and pulling closures into the global scope to aid compilation, is called [Lambda Lifting](https://en.wikipedia.org/wiki/Lambda_lifting). Its name is no coincidence - lambda functions need to undergo the same kind of transformation as our nested definitions (unlike nested definitions, though, lambda functions need to be named). This is why they are included in this post together with `let/in`!
|
||||
|
||||
What are lambda functions, by the way? A lambda function is just a function
|
||||
expression that doesn't have a name. For example, if we had Haskell code like
|
||||
this:
|
||||
|
||||
```Haskell
|
||||
double x = x + x
|
||||
doubleList xs = map double xs
|
||||
```
|
||||
|
||||
We could rewrite it using a lambda function as follows:
|
||||
|
||||
```Haskell
|
||||
doubleList xs = map (\x -> x + x) xs
|
||||
```
|
||||
|
||||
As you can see, a lambda is an expression in the form `\x -> y` where `x` can
|
||||
be any variable and `y` can be any expression (including another lambda).
|
||||
This represents a function that, when applied to a value `x`, will perform
|
||||
the computation given by `y`. Lambdas are useful when creating single-use
|
||||
functions that we don't want to make globally available.
|
||||
|
||||
Lifting lambda functions will effectively rewrite our program in the
|
||||
opposite direction to the one shown, replacing the lambda with a reference
|
||||
to a global declaration which will hold the function's body. Just like
|
||||
with `let/in`, we will represent captured variables using arguments
|
||||
and partial appliciation. For instance, when starting with:
|
||||
|
||||
```Haskell
|
||||
addToAll n xs = map (\x -> n + x) xs
|
||||
```
|
||||
|
||||
We would output the following:
|
||||
|
||||
```Haskell
|
||||
addToAll n xs = map (lambda n) xs
|
||||
lambda n x = n + x
|
||||
```
|
||||
|
||||
### Implementation
|
||||
Now that we understand what we have to do, it's time to jump straight into
|
||||
doing it. First, we need to refactor our current code to allow for the changes
|
||||
we're going to make; then, we will use the new tools we defined to implement `let/in` expressions and lambda functions.
|
||||
|
||||
#### Infrastructure Changes
|
||||
When finding captured variables, the notion of _free variables_ once again
|
||||
becomes important. Recall that a free variable in an expression is a variable
|
||||
that is defined outside of that expression. Consider, for example, the
|
||||
expression:
|
||||
|
||||
```Haskell
|
||||
let x = 5 in x + y
|
||||
```
|
||||
|
||||
In this expression, `x` is _not_ a free variable, since it's defined
|
||||
in the `let/in` expression. On the other hand, `y` _is_ a free variable,
|
||||
since it's not defined locally.
|
||||
|
||||
The algorithm that we used for computing free variables was rather biased.
|
||||
Previously, we only cared about the difference between a local variable
|
||||
(defined somewhere in a function's body, or referring to one of the function's
|
||||
parameters) and a global variable (referring to a global function).
|
||||
This shows in our code for `find_free`. Consider, for example, this snippet:
|
||||
|
||||
{{< codelines "C++" "compiler/11/ast.cpp" 33 36 >}}
|
||||
|
||||
We created bindings in our type environment whenever we saw a new variable
|
||||
being introduced, which led us to only count variables that we did not bind
|
||||
_anywhere_ as 'free'. This approach is no longer sufficient. Consider,
|
||||
for example, the following Haskell code:
|
||||
|
||||
```Haskell {linenos=table}
|
||||
someFunction x =
|
||||
let
|
||||
y = x + 5
|
||||
in
|
||||
x*y
|
||||
```
|
||||
|
||||
We can see that the variable `x` is introduced on line 1.
|
||||
Thus, our current algorithm will happily store `x` in an environment,
|
||||
and not count it as free. But clearly, the definition of `y` on line 3
|
||||
captures `x`! If we were to lift `y` into global scope, we would need
|
||||
to pass `x` to it as an argument. To fix this, we have to separate the creation
|
||||
and assignment of type environments from free variable detection. Why
|
||||
don't we start with `ast` and its descendants? Our signatures become:
|
||||
|
||||
```C++
|
||||
void ast::find_free(std::set<std::string>& into);
|
||||
type_ptr ast::typecheck(type_mgr& mgr, type_env_ptr& env);
|
||||
```
|
||||
|
||||
For the most part, the code remains unchanged. We avoid
|
||||
using `env` (and `this->env`), and default to marking
|
||||
any variable as a free variable:
|
||||
|
||||
{{< codelines "C++" "compiler/12/ast.cpp" 39 41 >}}
|
||||
|
||||
Since we no longer use the environment, we resort to an
|
||||
alternative method of removing bound variables. Here's
|
||||
`ast_case::find_free`:
|
||||
|
||||
{{< codelines "C++" "compiler/12/ast.cpp" 169 181 >}}
|
||||
|
||||
For each branch, we find the free variables. However, we
|
||||
want to avoid marking variables that were introduced through
|
||||
pattern matching as free (they are not). Thus, we use `pattern::find_variables`
|
||||
to see which of the variables were bound by that pattern,
|
||||
and remove them from the list of free variables. We
|
||||
can then safely add the list of free variables in the pattern to the overall
|
||||
list of free variables. Other `ast` descendants experience largely
|
||||
cosmetic changes (such as the removal of the `env` parameter).
|
||||
|
||||
Of course, we must implement `find_variables` for each of our `pattern`
|
||||
subclasses. Here's what I got for `pattern_var`:
|
||||
|
||||
{{< codelines "C++" "compiler/12/ast.cpp" 402 404 >}}
|
||||
|
||||
And here's an equally terse implementation for `pattern_constr`:
|
||||
|
||||
{{< codelines "C++" "compiler/12/ast.cpp" 417 419 >}}
|
||||
|
||||
We also want to update `definition_defn` with this change. Our signatures
|
||||
become:
|
||||
|
||||
```C++
|
||||
void definition_defn::find_free();
|
||||
void definition_defn::insert_types(type_mgr& mgr, type_env_ptr& env, visibility v);
|
||||
```
|
||||
|
||||
We'll get to the `visiblity` parameter later. The implementations
|
||||
are fairly simple. Just like `ast_case`, we want to erase each function's
|
||||
parameters from its list of free variables:
|
||||
|
||||
{{< codelines "C++" "compiler/12/definition.cpp" 13 18 >}}
|
||||
|
||||
Since `find_free` no longer creates any type bindings or environments,
|
||||
this functionality is shouldered by `insert_types`:
|
||||
|
||||
{{< codelines "C++" "compiler/12/definition.cpp" 20 32 >}}
|
||||
|
||||
Now that free variables are properly computed, we are able to move on
|
||||
to bigger and better things.
|
||||
|
||||
#### Nested Definitions
|
||||
At present, our code for typechecking the whole program is located in
|
||||
`main.cpp`:
|
||||
|
||||
{{< codelines "C++" "compiler/11/main.cpp" 43 61 >}}
|
||||
|
||||
This piece of code goes on. We now want this to be more general. Soon, `let/in`
|
||||
expressions with bring with them definitions that are inside other definitions,
|
||||
which will not be reachable at the top level. The fundamental topological
|
||||
sorting algorithm, though, will remain the same. We can abstract a series
|
||||
of definitions that need to be ordered and then typechecked into a new struct,
|
||||
`definition_group`:
|
||||
|
||||
{{< codelines "C++" "compiler/12/definition.hpp" 73 83 >}}
|
||||
|
||||
This will be exactly like a list of `defn`/`data` definitions we have at the
|
||||
top level, except now, it can also occur in other places, like `let/in`
|
||||
expressions. Once again, ignore for the moment the `visibility` field.
|
||||
|
||||
The way we defined function ordering requires some extra work from
|
||||
`definition_group`. Recall that conceptually, functions can only depend
|
||||
on other functions defined in the same `let/in` expression, or, more generally,
|
||||
in the same `definition_group`. This means that we now classify free variables
|
||||
in definitions into two categories: free variables that refer to "nearby"
|
||||
definitions (i.e. definitions in the same group) and free variables that refer
|
||||
to "far away" definitions. The "nearby" variables will be used to do
|
||||
topological ordering, while the "far away" variables can be passed along
|
||||
further up, perhaps into an enclosing `let/in` expression (for which "nearby"
|
||||
variables aren't actually free, since they are bound in the `let`). We
|
||||
implement this partitioning of variables in `definition_group::find_free`:
|
||||
|
||||
{{< codelines "C++" "compiler/12/definition.cpp" 94 105 >}}
|
||||
|
||||
Notice that we have added a new `nearby_variables` field to `definition_defn`.
|
||||
This is used on line 101, and will be once again used in `definition_group::typecheck`. Speaking of `typecheck`, let's look at its definition:
|
||||
|
||||
{{< codelines "C++" "compiler/12/definition.cpp" 107 145 >}}
|
||||
|
||||
This function is a little long, but conceptually, each `for` loop
|
||||
contains a step of the process:
|
||||
|
||||
* The first loop declares all data types, so that constructors can
|
||||
be verified to properly reference them.
|
||||
* The second loop creates all the data type constructors.
|
||||
* The third loop adds edges to our dependency graph.
|
||||
* The fourth loop performs typechecking on the now-ordered groups of mutually
|
||||
recursive functions.
|
||||
* The first inner loop inserts the types of all the functions into the environment.
|
||||
* The second inner loop actually performs typechecking.
|
||||
* The third inner loop makes as many things polymorphic as possible.
|
||||
|
||||
We can now adjust our `parser.y` to use a `definition_group` instead of
|
||||
two global vectors. First, we declare a global `definition_group`:
|
||||
|
||||
{{< codelines "C++" "compiler/12/parser.y" 10 10 >}}
|
||||
|
||||
Then, we adjust `definitions` to create `definition_group`s:
|
||||
|
||||
{{< codelines "text" "compiler/12/parser.y" 59 68 >}}
|
||||
|
||||
We can now adjust `main.cpp` to use the global `definition_group`. Among
|
||||
other changes (such as removing `extern` references to `vector`s, and updating
|
||||
function signatures) we also update the `typecheck_program` function:
|
||||
|
||||
{{< codelines "C++" "compiler/12/main.cpp" 41 49 >}}
|
||||
|
||||
Now, our code is ready for typechecking nested definitions, but not for
|
||||
compiling them. The main thing that we still have to address is the addition
|
||||
of new definitions to the global scope. Let's take a look at that next.
|
||||
|
||||
#### Global Definitions
|
||||
We want every function (and even non-function definitions that capture surrounding
|
||||
variables), regardless of whether or not it was declared in the global scope,
|
||||
to be processed and converted to LLVM code. The LLVM code conversion takes
|
||||
several steps. First, the function's AST is translated into G-machine
|
||||
instructions, which we covered in [part 5]({{< relref "05_compiler_execution.md" >}}),
|
||||
by a process we covered in [part 6]({{< relref "06_compiler_compilation.md" >}}).
|
||||
Then, an LLVM function is created for every function, and registered globally.
|
||||
Finally, the G-machine instructions are converted into LLVM IR, which is
|
||||
inserted into the previously created functions. These things
|
||||
can't be done in a single pass: at the very least, we can't start translating
|
||||
G-machine instructions into LLVM IR until functions are globally declared,
|
||||
because we would otherwise have no means of referencing other functions. It
|
||||
makes sense to me, then, to pull out all the 'global' definitions into
|
||||
a single top-level list (perhaps somewhere in `main.cpp`).
|
||||
|
||||
Let's start implementing this with a new `global_scope` struct. This
|
||||
struct will contain all of the global function and constructor definitions:
|
||||
|
||||
{{< codelines "C++" "compiler/12/global_scope.hpp" 42 55 >}}
|
||||
|
||||
This struct will allow us to keep track of all the global definitions,
|
||||
emitting them as we go, and then coming back to them as necessary.
|
||||
There are also signs of another piece of functionality: `occurence_count`
|
||||
and `mangle_name`. These two will be used to handle duplicate names.
|
||||
|
||||
We cannot have two global functions named the same thing, but we can
|
||||
easily imagine a situation in which two separate `let/in` expressions define
|
||||
a variable like `x`, which then needs to be lifted to the global scope. We
|
||||
resolve such conflicts by slightly changing - "mangling" - the name of
|
||||
one of the resulting global definitions. We allow the first global definition
|
||||
to be named the same as it was originally (in our example, this would be `x`).
|
||||
However, if we detect that a global definition `x` already exists (we
|
||||
track this using `occurence_count`), we rename it to `x_1`. Subsequent
|
||||
global definitions will end up being named `x_2`, `x_3`, and so on.
|
||||
|
||||
Alright, let's take a look at `global_function` and `global_constructor`.
|
||||
Here's the former:
|
||||
|
||||
{{< codelines "C++" "compiler/12/global_scope.hpp" 11 27 >}}
|
||||
|
||||
There's nothing really surprising here: all of the fields
|
||||
are reminiscent of `definition_defn`, though some type-related variables
|
||||
are missing. We also include the three compilation-related methods,
|
||||
`compile`, `declare_llvm`, and `generate_llvm`, which were previously in `definition_defn`. Let's look at `global_constructor` now:
|
||||
|
||||
{{< codelines "C++" "compiler/12/global_scope.hpp" 29 40 >}}
|
||||
|
||||
This maps pretty closely to a single `definition_data::constructor`.
|
||||
There's a difference here that is not clear at a glance, though. Whereas
|
||||
the `name` in a `definition_defn` or `definition_data` refers to the
|
||||
name as given by the user in the code, the `name` of a `global_function`
|
||||
or `global_constructor` has gone through mangling, and thus, should be
|
||||
unique.
|
||||
|
||||
Let's now look at the implementation of these structs' methods. The methods
|
||||
`add_function` and `add_constructor` are pretty straightforward. Here's
|
||||
the former:
|
||||
|
||||
{{< codelines "C++" "compiler/12/global_scope.cpp" 39 43 >}}
|
||||
|
||||
And here's the latter:
|
||||
|
||||
{{< codelines "C++" "compiler/12/global_scope.cpp" 45 49 >}}
|
||||
|
||||
In both of these functions, we return a reference to the new global
|
||||
definition we created. This helps us access the mangled `name` field,
|
||||
and, in the case of `global_function`, inspect the `ast_ptr` that represents
|
||||
its body.
|
||||
|
||||
Next, we have `global_scope::compile` and `global_scope::generate_llvm`,
|
||||
which encapsulate these operations on all global definitions. Their
|
||||
implementations are very straightforward, and are similar to the
|
||||
`gen_llvm` function we used to have in our `main.cpp`:
|
||||
|
||||
{{< codelines "C++" "compiler/12/global_scope.cpp" 51 67 >}}
|
||||
|
||||
Finally, we have `mangle`, which takes care of potentially duplicate
|
||||
variable names:
|
||||
|
||||
{{< codelines "C++" "compiler/12/global_scope.cpp" 69 83 >}}
|
||||
|
||||
Let's move on to the global definition structs.
|
||||
The `compile`, `declare_llvm`, and `generate_llvm` methods for
|
||||
`global_function` are pretty much the same as those that we used to have
|
||||
in `definition_defn`:
|
||||
|
||||
{{< codelines "C++" "compiler/12/global_scope.cpp" 4 24 >}}
|
||||
|
||||
The same is true for `global_constructor` and its method `generate_llvm`:
|
||||
|
||||
{{< codelines "C++" "compiler/12/global_scope.cpp" 26 37 >}}
|
||||
|
||||
Recall that in this case, we need not have two methods for declaring
|
||||
and generating LLVM, since constructors don't reference other constructors,
|
||||
and are always generated before any function definitions.
|
||||
|
||||
#### Visibility
|
||||
Should we really be turning _all_ free variables in a function definition
|
||||
into arguments? Consider the following piece of Haskell code:
|
||||
|
||||
```Haskell {linenos=table}
|
||||
add x y = x + y
|
||||
mul x y = x * y
|
||||
something = mul (add 1 3) 3
|
||||
```
|
||||
|
||||
In the definition of `something`, `mul` and `add` occur free.
|
||||
A very naive lifting algorithm might be tempted to rewrite such a program
|
||||
as follows:
|
||||
|
||||
```Haskell {linenos=table}
|
||||
add x y = x + y
|
||||
mul x y = x * y
|
||||
something' add mul = mul (add 1 3) 3
|
||||
something = something' add mul
|
||||
```
|
||||
|
||||
But that's absurd! Not only are `add` and `mul` available globally,
|
||||
but such a rewrite generates another definition with free variables,
|
||||
which means we didn't really improve our program in any way. From this
|
||||
example, we can see that we don't want to be turning reference to global
|
||||
variables into function parameters. But how can we tell if a variable
|
||||
we're trying to operate on is global or not? I propose a flag in our
|
||||
`type_env`, which we'll augment to be used as a symbol table. To do
|
||||
this, we update the implementation of `type_env` to map variables to
|
||||
values of a struct `variable_data`:
|
||||
|
||||
{{< codelines "C++" "compiler/12/type_env.hpp" 14 23 >}}
|
||||
|
||||
The `visibility` enum is defined as follows:
|
||||
|
||||
{{< codelines "C++" "compiler/12/type_env.hpp" 11 11 >}}
|
||||
|
||||
As you can see from the above snippet, we also added a `mangled_name` field
|
||||
to the new `variable_data` struct. We will be using this field shortly. We
|
||||
also add a few methods to our `type_env`, and end up with the following:
|
||||
|
||||
{{< codelines "C++" "compiler/12/type_env.hpp" 32 45 >}}
|
||||
|
||||
We will come back to `find_free` and `find_free_except`, as well as
|
||||
`set_mangled_name` and `get_mangled_name`. For now, we just adjust `bind` to
|
||||
take a visibility parameter that defaults to `local`, and implement
|
||||
`is_global`:
|
||||
|
||||
{{< codelines "C++" "compiler/12/type_env.cpp" 27 32 >}}
|
||||
|
||||
Remember the `visibility::global` in `parser.y`? This is where that comes in.
|
||||
Specifically, we recall that `definition_defn::insert_types` is responsible
|
||||
for placing function types into the environment, making them accessible
|
||||
during typechecking later. At this time, we already need to know whether
|
||||
or not the definitions are global or local (so that we can create the binding).
|
||||
Thus, we add `visibility` as a parameter to `insert_types`:
|
||||
|
||||
{{< codelines "C++" "compiler/12/definition.hpp" 44 44 >}}
|
||||
|
||||
Since we are now moving from manually wrangling definitions towards using
|
||||
`definition_group`, we make it so that the group itself provides this
|
||||
argument. To do this, we add the `visibility` field from before to it,
|
||||
and set it in the parser. One more thing: since constructors never
|
||||
capture variables, we can always move them straight to the global
|
||||
scope, and thus, we'll always mark them with `visibility::global`.
|
||||
|
||||
#### Managing Mangled Names
|
||||
Just mangling names is not enough. Consider the following program:
|
||||
|
||||
```text {linenos=table}
|
||||
defn packOne x = {
|
||||
let {
|
||||
data Packed a = { Pack a }
|
||||
} in {
|
||||
Pack x
|
||||
}
|
||||
}
|
||||
defn packTwo x = {
|
||||
let {
|
||||
data Packed a = { Pack a }
|
||||
} in {
|
||||
Pack x
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{{< sidenote "right" "lifting-types-note" "Lifting the data type declarations" >}}
|
||||
We are actually not <em>quite</em> doing something like the following snippet.
|
||||
The reason for this is that we don't mangle the names for types. I pointed
|
||||
out this potential issue in a sidenote in the previous post. Since the size
|
||||
of this post is already balooning, I will not deal with this issue here.
|
||||
Even at the end of this post, our compiler will not be able to distinguish
|
||||
between the two <code>Packed</code> types. We will hopefully get to it later.
|
||||
{{< /sidenote >}} and their constructors into the global
|
||||
scope gives us something like:
|
||||
|
||||
``` {linenos=table}
|
||||
data Packed a = { Pack a }
|
||||
data Packed_1 a = { Pack_1 a }
|
||||
defn packOne x = { Pack x }
|
||||
defn packTwo x = { Pack_1 x }
|
||||
```
|
||||
|
||||
Notice that we had to rename one of the calls to `Pack` to be a call to
|
||||
be `Pack_1`. To actually change our AST to reference `Pack_1`, we'd have
|
||||
to traverse the whole tree, and make sure to keep track of definitions
|
||||
that could shadow `Pack` further down. This is cumbersome. Instead, we
|
||||
can mark a variable as referring to a mangled version of itself, and
|
||||
access this information when needed. To do this, we add the `mangled_name`
|
||||
field to the `variable_data` struct as we've seen above, and implement
|
||||
the `set_mangled_name` and `get_mangled_name` methods. The former:
|
||||
|
||||
{{< codelines "C++" "compiler/12/type_env.cpp" 34 37 >}}
|
||||
|
||||
And the latter:
|
||||
|
||||
{{< codelines "C++" "compiler/12/type_env.cpp" 39 45 >}}
|
||||
|
||||
We don't allow `set_mangled_name` to affect variables that are declared
|
||||
above the receiving `type_env`, and use the empty string as a 'none' value.
|
||||
Now, when lifting data type constructors, we'll be able to use
|
||||
`set_mangled_name` to make sure constructor calls are made correctly. We
|
||||
will also be able to use this in other cases, like the translation
|
||||
of local function definitions.
|
||||
|
||||
#### New AST Nodes
|
||||
Finally, it's time for us to add new AST nodes to our language.
|
||||
Specifically, these nodes are `ast_let` (for `let/in` expressions)
|
||||
and `ast_lambda` for lambda functions. We declare them as follows:
|
||||
|
||||
{{< codelines "C++" "compiler/12/ast.hpp" 131 166 >}}
|
||||
|
||||
In `ast_let`, the `definitions` field corresponds to the original definitions
|
||||
given by the user in the program, and the `in` field corresponds to the
|
||||
expression which uses these definitions. In the process of lifting, though,
|
||||
we eventually transfer each of the definitions to the global scope, replacing
|
||||
their right hand sides with partial applications. After this transformation,
|
||||
all the data type definitions are effectively gone, and all the function
|
||||
definitions are converted into the simple form `x = f a1 ... an`. We hold
|
||||
these post-transformation equations in the `translated_definitions` field,
|
||||
and it's them that we compile in this node's `compile` method.
|
||||
|
||||
In `ast_lambda`, we allow multiple parameters (like Haskell's `\x y -> x + y`).
|
||||
We store these parameters in the `params` field, and we store the lambda's
|
||||
expression in the `body` field. Just like `definition_defn`,
|
||||
the `ast_lambda` node maintains a separate environment in which its children
|
||||
have been bound, and a list of variables that occur freely in its body. The
|
||||
former is used for typechecking, while the latter is used for lifting.
|
||||
Finally, the `translated` field holds the lambda function's form
|
||||
after its body has been transformed into a global function. Similarly to
|
||||
`ast_let`, this node will be in the form `f a1 ... an`.
|
||||
|
||||
The
|
||||
observant reader will have noticed that we have a new method: `translate`.
|
||||
This is a new method for all `ast` descendants, and will implement the
|
||||
steps of moving definitions to the global scope and transforming the
|
||||
program. Before we get to it, though, let's look at the other relevant
|
||||
pieces of code for `ast_let` and `ast_lambda`. First, their grammar
|
||||
rules in `parser.y`:
|
||||
|
||||
{{< codelines "text" "compiler/12/parser.y" 107 115 >}}
|
||||
|
||||
This is pretty similar to the rest of the grammar, so I will give this no
|
||||
further explanation. Next, their `find_free` and `typecheck` code.
|
||||
We can start with `ast_let`:
|
||||
|
||||
{{< codelines "C++" "compiler/12/ast.cpp" 275 289 >}}
|
||||
|
||||
As you can see, `ast_let::find_free` works in a similar manner to `ast_case::find_free`.
|
||||
It finds the free variables in the `in` node as well as in each of the definitions
|
||||
(taking advantage of the fact that `definition_group::find_free` populates the
|
||||
given set with "far away" free variables). It then filters out any variables bound in
|
||||
the `let` from the set of free variables in `in`, and returns the result.
|
||||
|
||||
Typechecking in `ast_let` relies on `definition_group::typecheck`, which holds
|
||||
all of the required functionality for checking the new definitions.
|
||||
Once the definitions are typechecked, we use their type information to
|
||||
typecheck the `in` part of the expression (passing `definitions.env` to the
|
||||
call to `typecheck` to make the new definitions visible).
|
||||
|
||||
Next, we look at `ast_lambda`:
|
||||
|
||||
{{< codelines "C++" "compiler/12/ast.cpp" 344 366 >}}
|
||||
|
||||
Again, `ast_lambda::find_free` works similarly to `definition_defn`, stripping
|
||||
the variables expected by the function from the body's list of free variables.
|
||||
Also like `definition_defn`, this new node remembers the free variables in
|
||||
its body, which we will later use for lifting.
|
||||
|
||||
Typechecking in this node also proceeds similarly to `definition_defn`. We create
|
||||
new type variables for each parameter and for the return value, and build up
|
||||
a function type called `full_type`. We then typecheck the body using the
|
||||
new environment (which now includes the variables), and return the function type we came up with.
|
||||
|
||||
#### Translation
|
||||
Recalling the transformations we described earlier, we can observe two
|
||||
major steps to what we have to do:
|
||||
|
||||
1. Move the body of the original definition into its own
|
||||
global definition, adding all the captured variables as arguments.
|
||||
2. Replace the right hand side of the `let/in` expression with an application
|
||||
of the global definition to the variables it requires.
|
||||
|
||||
We will implement these in a new `translate` method, with the following
|
||||
signature:
|
||||
|
||||
```C++
|
||||
void ast::translate(global_scope& scope);
|
||||
```
|
||||
|
||||
The `scope` parameter and its `add_function` and `add_constructor` methods will
|
||||
be used to add definitions to the global scope. Each AST node will also
|
||||
use this method to implement the second step. Currently, only
|
||||
`ast_let` and `ast_lambda` will need to modify themselves - all other
|
||||
nodes will simply recursively call this method on their children. Let's jump
|
||||
straight into implementing this method for `ast_let`:
|
||||
|
||||
{{< codelines "C++" "compiler/12/ast.cpp" 291 316 >}}
|
||||
|
||||
Since data type definitions don't really depend on anything else, we process
|
||||
them first. This amounts to simply calling the `definition_data::into_globals`
|
||||
method, which itself simply calls `global_scope::add_constructor`:
|
||||
|
||||
{{< codelines "C++" "compiler/12/definition.cpp" 86 92 >}}
|
||||
|
||||
Note how `into_globals` updates the mangled name of its constructor
|
||||
via `set_mangled_name`. This will help us decide which global
|
||||
function to call during code generation. More on that later.
|
||||
|
||||
Starting with line 295, we start processing the function definitions
|
||||
in the `let/in` expression. We remember how many arguments were
|
||||
explicitly added to the function definition, and then call the
|
||||
definition's `into_global` method. This method is implemented
|
||||
as follows:
|
||||
|
||||
{{< codelines "C++" "compiler/12/definition.cpp" 40 49 >}}
|
||||
|
||||
First, this method collects all the non-global free variables in
|
||||
its body, which will need to be passed to the global definition
|
||||
as arguments. It then combines this list with the arguments
|
||||
the user explicitly added to it, recursively translates
|
||||
its body, and creates a new global definition using `add_function`.
|
||||
|
||||
We return to `ast_let::translate` at line 299. Here,
|
||||
we determine how many variables ended up being captured, by
|
||||
subtracting the number of explicit parameters from the total
|
||||
number of parameters the new global definition has. This number,
|
||||
combined with the fact that we added all the 'implict' arguments
|
||||
to the function to the beginning of the list, will let us
|
||||
iterate over all implict arguments, creating a chain of partial
|
||||
function applications.
|
||||
|
||||
But how do we build the application? We could use the mangled name
|
||||
of the function, but this seems inelegant, especially since we
|
||||
alreaady keep track of mangling information in `type_env`. Instead,
|
||||
we create a new, local environment, in which we place an updated
|
||||
binding for the function, marking it global, and setting
|
||||
its mangled name to the one generated by `global_sope`. This work is done
|
||||
on lines 301-303. We create a reference to the global function
|
||||
using the new environment on lines 305 and 306, and apply it to
|
||||
all the implict arguments on lines 307-313. Finally, we
|
||||
add the new 'basic' equation into `translated_definitions`.
|
||||
|
||||
Let's take a look at translating `ast_lambda` next:
|
||||
|
||||
{{< codelines "C++" "compiler/12/ast.cpp" 368 392 >}}
|
||||
|
||||
Once again, on lines 369-375 we find all the arguments to the
|
||||
global definition. On lines 377-382 we create a new global
|
||||
function and a mangled environment, and start creating the
|
||||
chain of function applications. On lines 384-390, we actually
|
||||
create the arguments and apply the function to them. Finally,
|
||||
on line 391, we store this new chain of applications in the
|
||||
`translated` field.
|
||||
|
||||
#### Compilation
|
||||
There's still another piece of the puzzle missing, and
|
||||
that's how we're going to compile `let/in` expressions into
|
||||
G-machine instructions. We have allowed these expressions
|
||||
to be recursive, and maybe even mutually recursive. This
|
||||
worked fine with global definitions; instead of specifying
|
||||
where on the stack we can find the reference to a global
|
||||
function, we just created a new global node, and called
|
||||
it good. Things are different now, though, because the definitions
|
||||
we're referencing aren't _just_ global functions; they are partial
|
||||
applications of a global function. And to reference themselves,
|
||||
or their neighbors, they have to have a handle on their own nodes. We do this
|
||||
using an instruction that we foreshadowed in part 5, but didn't use
|
||||
until just now: __Alloc__.
|
||||
|
||||
__Alloc__ creates placeholder nodes on the stack. These nodes
|
||||
are indirections, the same kind that we use for lazy evaluation
|
||||
and sharing elsewhere. We create an indirection node for every
|
||||
definition that we then build; when an expression needs access
|
||||
to a definition, we give it the indirection node. After
|
||||
building the partial application graph for an expression,
|
||||
we use __Update__, making the corresponding indirection
|
||||
point to this new graph. This way, the 'handle' to a
|
||||
definition is always accessible, and once the definition's expression
|
||||
is built, the handle correctly points to it. Here's the implementation:
|
||||
|
||||
{{< codelines "C++" "compiler/12/ast.cpp" 319 332 >}}
|
||||
|
||||
First, we create the __Alloc__ instruction. Then, we update
|
||||
our environment to map each definition name to a location
|
||||
within the newly allocated batch of nodes. Since we iterate
|
||||
the definitions in order, 'pushing' them into our environment,
|
||||
we end up with the convention of having the later definitions
|
||||
closer to the top of the G-machine stack. Thus, when we
|
||||
iterate the definitions again, this time to compile their
|
||||
bodies, we have to do so starting with the highest offset,
|
||||
and working our way down to __Update__-ing the top of the stack.
|
||||
Once the definitions have been compiled, we proceed to compiling
|
||||
the `in` part of the expression as normal, using our updated
|
||||
environment. Finally, we use __Slide__ to get rid of the definition
|
||||
graphs, cleaning up the stack.
|
||||
|
||||
Compiling the `ast_lambda` is far more straightforward. We just
|
||||
compile the resulting partial application as we normally would have:
|
||||
|
||||
{{< codelines "C++" "compiler/12/ast.cpp" 394 396 >}}
|
||||
|
||||
One more thing. Let's adopt the convention of storing __mangled__
|
||||
names into the compilation environment. This way, rather than looking up
|
||||
mangled names only for global functions, which would be a 'gotcha'
|
||||
for anyone working on the compiler, we will always use the mangled
|
||||
names during compilation. To make this change, we make sure that
|
||||
`ast_case` also uses `mangled_name`:
|
||||
|
||||
{{< codelines "C++" "compiler/12/ast.cpp" 242 242 >}}
|
||||
|
||||
We also update the logic for `ast_lid::compile` to use the mangled
|
||||
name information:
|
||||
|
||||
{{< codelines "C++" "compiler/12/ast.cpp" 52 58 >}}
|
||||
|
||||
#### Fixing Type Generalization
|
||||
This is a rather serious bug that made its way into the codebase
|
||||
since part 10. Recall that we can only generalize type variables
|
||||
that are free in the environment. Thus far, we haven't done that,
|
||||
and we really should: I ran into incorrectly inferred types
|
||||
in my first test of the `let/in` language feature.
|
||||
|
||||
We need to make our code capable of finding free variables in the
|
||||
type environment. This requires the `type_mgr`, which associates
|
||||
with type variables the real types they represent, if any. We
|
||||
thus create methods with signatures as follows:
|
||||
|
||||
```C++
|
||||
void type_env::find_free(const type_mgr& mgr, std::set<std::string>& into) const;
|
||||
void type_env::find_free_except(const type_mgr& mgr, const std::string& avoid,
|
||||
std::set<std::string>& into) const;
|
||||
```
|
||||
|
||||
Why `find_free_except`? When generalizing a variable whose type was already
|
||||
stored in the environment, all the type variables we could generalize would
|
||||
not be 'free'. If they only occur in the type we're generalizing, though,
|
||||
we shouldn't let that stop us! More generally, if we see type variables that
|
||||
are only found in the same mutually recursive group as the binding we're
|
||||
generalizing, we are free to generalize them too. Thus, we pass in
|
||||
a reference to a `group`, and check if a variable is a member of that group
|
||||
before searching it for free type variables. The implementations of the two
|
||||
methods are straightforward:
|
||||
|
||||
{{< codelines "C++" "compiler/12/type_env.cpp" 4 18 >}}
|
||||
|
||||
Note that `find_free_except` calls `find_free` in its recursive call. This
|
||||
is not a bug: we _do_ want to include free type variables from bindings
|
||||
that have the same name as the variable we're generalizing, but aren't found
|
||||
in the same scope. As far as we're concerned, they're different variables!
|
||||
The two methods use another `find_free` method which we add to `type_mgr`:
|
||||
|
||||
{{< codelines "C++" "compiler/12/type.cpp" 206 219 >}}
|
||||
|
||||
This one is a bit of a hack. Typically, while running `find_free`, a
|
||||
`type_mgr` will resolve any type variables. However, variables from the
|
||||
`forall` quantifier of a type scheme should not be resolved, since they
|
||||
are explicitly generic. To prevent the type manager from erroneously resolving
|
||||
such type variables, we create a new type manager that does not have
|
||||
these variables bound to anything, and thus marks them as free. We then
|
||||
filter these variables out of the final list of free variables.
|
||||
|
||||
Finally, `generalize` makes sure not to use variables that it finds free:
|
||||
|
||||
{{< codelines "C++" "compiler/12/type_env.cpp" 68 81 >}}
|
||||
|
||||
#### Putting It All Together
|
||||
All that's left is to tie the parts we've created into one coherent whole
|
||||
in `main.cpp`. First of all, since we moved all of the LLVM-related
|
||||
code into `global_scope`, we can safely replace that functionality
|
||||
in `main.cpp` with a method call:
|
||||
|
||||
{{< codelines "C++" "compiler/12/main.cpp" 121 132 >}}
|
||||
|
||||
On the other hand, we need top-level logic to handle `definition_group`s.
|
||||
This is pretty straightforward, and the main trick is to remember to
|
||||
update the function's mangled name. Right now, depending on the choice
|
||||
of manging algorithm, it's possible even for top-level functions to
|
||||
have their names changed, and we must account for that. The whole code is:
|
||||
|
||||
{{< codelines "C++" "compiler/12/main.cpp" 52 62 >}}
|
||||
|
||||
Finally, we call `global_scope`'s methods in `main()`:
|
||||
|
||||
{{< codelines "C++" "compiler/12/main.cpp" 148 151 >}}
|
||||
|
||||
That's it! Please note that I've mentioned or hinted at minor changes to the
|
||||
codebase. Detailing every single change this late into the project is
|
||||
needlessly time consuming and verbose; Gitea reports that I've made 677
|
||||
insertions into and 215 deletions from the code. As always, I provide
|
||||
the [source code for the compiler](https://dev.danilafe.com/Web-Projects/blog-static/src/branch/master/code/compiler/12), and you can also take a look at the
|
||||
[Gitea-generated diff](https://dev.danilafe.com/Web-Projects/blog-static/compare/1905601aaa96d11c771eae9c56bb9fc105050cda...21851e3a9c552383ee8c4bc878ea06e7d28c333e)
|
||||
at the time of writing. If you want to follow along, feel free to check
|
||||
them out!
|
||||
|
||||
### Running Our Programs
|
||||
It's important to test all the language features that we just added. This
|
||||
includes recursive definitions, nested function dependency cycles, and
|
||||
uses of lambda functions. Some of the following examples will be rather
|
||||
silly, but they should do a good job of checking that everything works
|
||||
as we expect. Let's start with a simple use of a recursive definition
|
||||
inside a `let/in`. A classic definition in that form is of `fix`
|
||||
(the fixpoint combinator):
|
||||
|
||||
```Haskell
|
||||
fix f = let x = f x in x
|
||||
```
|
||||
|
||||
This defines `x` to be `f x`, which by substitution becomes `f (f x)`, and then
|
||||
`f (f (f x))` and so on. The fixpoint combinator allows one to write a
|
||||
recursive function that doesn't use its own name in the body. Rather,
|
||||
we write a function expecting to receive 'itself' as a value:
|
||||
|
||||
```Haskell
|
||||
fix :: (a -> a) -> a
|
||||
|
||||
factRec :: (Int -> Int) -> Int -> Int
|
||||
factRec f x = if x == 0 then 1 else x * f x
|
||||
|
||||
fact :: Int -> Int
|
||||
fact = fix factRec
|
||||
```
|
||||
|
||||
Notice that `factRec` doesn't reference itself, but rather takes
|
||||
as argument a function it expects to be 'factorial' called `f`,
|
||||
and uses that in its recursive case. We can write something similar
|
||||
in our language, perhaps to create an infinite list of ones:
|
||||
|
||||
{{< codeblock "text" "compiler/12/examples/fixpoint.txt" >}}
|
||||
|
||||
We want `sumTwo` to take the first two elements from the list,
|
||||
and return their sum. For an infinite list of ones, we expect
|
||||
this sum to be equal to 2, and it is:
|
||||
|
||||
```
|
||||
Result: 2
|
||||
```
|
||||
|
||||
Next, let's try to define a function which has a mutually recursive pair
|
||||
of definitions inside of a `let/in`. Let's also make these expressions
|
||||
reference a function from the global scope, so that we know our
|
||||
dependency tracking works as expected:
|
||||
|
||||
{{< codeblock "text" "compiler/12/examples/letin.txt" >}}
|
||||
|
||||
Here, we have a function `mergeUntil` which, given two lists
|
||||
and a predicate, combines the two lists as long as
|
||||
the predicate returns `True`. It does so using a convoluted
|
||||
pair of mutually recursive functions, one of which
|
||||
unpacks the left list, and the other the right. Each of the
|
||||
functions calls the global function `if`. We also use two
|
||||
definitions inside of `main` to create the two lists we're
|
||||
going to merge. The compiler outputs the following (correct)
|
||||
types:
|
||||
|
||||
```
|
||||
const: forall bb bc . bc -> bb -> bc
|
||||
if: Bool* -> List* Int* -> List* Int* -> List* Int*
|
||||
main: Int*
|
||||
mergeUntil: List* Int* -> List* Int* -> (Int* -> Bool*) -> List* Int*
|
||||
sum: List* Int* -> Int*
|
||||
```
|
||||
|
||||
And the result is 21, as would be expected from the sum of the numbers 1-6:
|
||||
|
||||
```
|
||||
Result: 21
|
||||
```
|
||||
|
||||
Let's try lambda functions now. We can try use them for a higher-order function
|
||||
like `map`:
|
||||
|
||||
{{< codeblock "text" "compiler/12/examples/lambda.txt" >}}
|
||||
|
||||
In this example, we first double every element in the list, then square it,
|
||||
and finally take the sum. This should give us 4+16+36 = 56, and so it does:
|
||||
|
||||
```
|
||||
Result: 56
|
||||
```
|
||||
|
||||
Finally, let's do some magic with a locally-declared data type. We'll make a
|
||||
"packer" that creates a wrapped instance of a type, `Packed a`. Since the
|
||||
constructor of this data type is not globally visible, it's not possible
|
||||
to get the value back out, except by using an 'unpacking' function that
|
||||
we provide:
|
||||
|
||||
{{< codeblock "text" "compiler/12/examples/packed.txt" >}}
|
||||
|
||||
Here, the `packer` definition returns a pair of the 'packing'
|
||||
and 'unpacking' functions. The 'packing' function simply applies
|
||||
the consntructor of `Packed` to its argument, while the 'unpacking'
|
||||
function performs pattern matching (which is possible since the
|
||||
data type is still in scope there). We expect `unpack (pack 3)` to
|
||||
return 3, and it does:
|
||||
|
||||
```
|
||||
Result: 3
|
||||
```
|
||||
|
||||
Trying to pattern match, though, doesn't work, just like we would want!
|
||||
|
||||
This is enough to convince me that our changes do, indeed, work! Of
|
||||
the 'major' components that I wanted to cover, only __Input/Output__
|
||||
remains! Additionally, a [lobste.rs](https://lobste.rs) user suggested
|
||||
that we also cover namespacing, and perhaps we will look into that as well.
|
||||
Before either of those things, though, I think that I want to go through
|
||||
the compiler and perform another round of improvements, similarly to
|
||||
[part 4]({{< relref "04_compiler_improvements" >}}). It's hard to do a lot
|
||||
of refactoring while covering new content, since major changes need to
|
||||
be explained and presented for the post to make sense. I hope to see
|
||||
you in these future posts!
|
||||
|
||||
@@ -12,7 +12,7 @@ __py-starbound__, nicely enough, actually has a file named `FORMATS.md`. This fi
|
||||
> This section will contain information on how to retrieve a value from a BTreeDB5 database.
|
||||
|
||||
Not very helpful. Before I go into what I managed to determine from the code, we may first take a look at one thing that we already know about the world format - it is a [B-Tree](https://en.wikipedia.org/wiki/B-tree).
|
||||
## Binary Search Trees
|
||||
### Binary Search Trees
|
||||
The B-Tree is a generalization of a Binary Search Tree, or BST for short. Binary Search trees (and B-Trees in general) operate on data that can be ordered consistently, the simplest example being numbers. For instance, as an example, I'll be using a BST that holds integers. A BST is made up of nodes, objects that actually hold the pieces of data that the tree itself organizes.
|
||||
|
||||
In a BST, the nodes are organized in a simple way. Each node can have up to two _children_ (sub-nodes), and each of those can have up to two children, etc. The children are generally classified as _right_ and _left_. Conventionally, left children always have a value that is below (or comes before) the value of the node whose child they are (their _parent_), and right children have a bigger value.
|
||||
@@ -45,7 +45,7 @@ __Although the average efficiency of a Binary Search Tree is \\(O(\log n)\\), me
|
||||
|
||||
This isn't good enough, and many clever algorithms have been invented to speed up the lookup of the tree by making sure that it remains _balanced_ - that is, it _isn't_ arranged like a simple list. Some of these algorithms include [Red-Black Trees](https://en.wikipedia.org/wiki/Red%E2%80%93black_tree), [AVL Trees](https://en.wikipedia.org/wiki/AVL_tree), and, of course, B-Trees.
|
||||
|
||||
## B-Trees
|
||||
### B-Trees
|
||||
B-Trees are a generalization of Binary Search Trees. That means that every Binary Search Tree is a B-Tree, but not all B-Trees are BSTs. The key difference lies in the fact that B-Trees' nodes aren't limited to having only two child nodes, and can also have more than one value.
|
||||
|
||||
Each B-Tree node is a sorted array of values. That is, instead of a single number like the BST that we've looked at, it has multiple, and these numbers _must_ be sorted. Below are some examples of B-Tree nodes:
|
||||
@@ -64,7 +64,7 @@ This is solved using another property of B-Trees - the number of children of a n
|
||||
|
||||
If we were looking for the number 15, we'd look between the 10 and the 20, examining the 2nd node, and if we were looking for 45 we'd look past the 30, at the 4th node.
|
||||
|
||||
## Starbound B-Trees and BTreeDB5
|
||||
### Starbound B-Trees and BTreeDB5
|
||||
The BTreeDB5 data structure uses something other than integers for its keys - it uses sequences of bytes. These bytes are compared in a very similar fashion to integers. The game first looks at the first number in the sequence of bytes (like the largest digit in an integer), and if that's the same, moves on to the next one. Also, Starbound B-Trees not only have the values, or _keys_, that they use to find data, but the data itself.
|
||||
|
||||
The "nodes" in the BTreeDB are called "blocks" and are one of three types - "index", "leaf", and "free" nodes. "Index" nodes are like the `(10, 20, 30)` node in the above example - they point to other nodes, but actually store no data themselves. The "leaf" nodes actually contain the data, and, if that data is longer than the maximum block size, "leaf" nodes contain the index of the next leaf node where the user might continue to read the data. The "free" nodes are simply free data, empty and ready for Starbound to fill them with something useful.
|
||||
|
||||
47
themes/vanilla/assets/scss/margin.scss
Normal file
47
themes/vanilla/assets/scss/margin.scss
Normal file
@@ -0,0 +1,47 @@
|
||||
@import "variables.scss";
|
||||
@import "mixins.scss";
|
||||
|
||||
$margin-width: 30rem;
|
||||
$margin-inner-offset: 0.5rem;
|
||||
$margin-outer-offset: 1rem;
|
||||
|
||||
@mixin below-two-margins {
|
||||
@media screen and
|
||||
(max-width: $container-width-threshold +
|
||||
2 * ($margin-width + $margin-inner-offset + $margin-outer-offset)) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin below-one-margin {
|
||||
@media screen and
|
||||
(max-width: $container-width-threshold +
|
||||
($margin-width + $margin-inner-offset + $margin-outer-offset)) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin margin-content {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: $margin-width;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@mixin margin-content-left {
|
||||
left: 0;
|
||||
margin-left: -($margin-width + $container-min-padding + $margin-inner-offset);
|
||||
|
||||
@include below-two-margins {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin margin-content-right {
|
||||
right: 0;
|
||||
margin-right: -($margin-width + $container-min-padding + $margin-inner-offset);
|
||||
|
||||
@include below-one-margin {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
}
|
||||
|
||||
@mixin below-container-width {
|
||||
@media screen and (max-width: $container-width){
|
||||
@media screen and (max-width: $container-width-threshold){
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,10 @@
|
||||
@import "variables.scss";
|
||||
@import "mixins.scss";
|
||||
@import "margin.scss";
|
||||
|
||||
$sidenote-accommodate-shrink: 10rem;
|
||||
$sidenote-width: 30rem;
|
||||
$sidenote-offset: 1.5rem;
|
||||
$sidenote-padding: 1rem;
|
||||
$sidenote-highlight-border-width: .2rem;
|
||||
|
||||
@mixin below-two-sidenotes {
|
||||
@media screen and
|
||||
(max-width: $container-width +
|
||||
2 * ($sidenote-width + 2 * $sidenote-offset)) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin below-one-sidenote {
|
||||
@media screen and
|
||||
(max-width: $container-width +
|
||||
($sidenote-width + 3 * $sidenote-offset)) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
.sidenote {
|
||||
&:hover {
|
||||
.sidenote-label {
|
||||
@@ -48,25 +30,19 @@ $sidenote-highlight-border-width: .2rem;
|
||||
}
|
||||
|
||||
.sidenote-content {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: $sidenote-width;
|
||||
@include margin-content;
|
||||
@include bordered-block;
|
||||
margin-top: -1.5rem;
|
||||
padding: $sidenote-padding;
|
||||
text-align: left;
|
||||
|
||||
&.sidenote-right {
|
||||
right: 0;
|
||||
margin-right: -($sidenote-width + $sidenote-offset);
|
||||
@include margin-content-right;
|
||||
}
|
||||
|
||||
&.sidenote-left {
|
||||
left: 0;
|
||||
margin-left: -($sidenote-width + $sidenote-offset);
|
||||
@include margin-content-left;
|
||||
}
|
||||
|
||||
@include bordered-block;
|
||||
padding: $sidenote-padding;
|
||||
box-sizing: border-box;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.sidenote-delimiter {
|
||||
@@ -78,36 +54,22 @@ $sidenote-highlight-border-width: .2rem;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
width: 100%;
|
||||
display: none;
|
||||
|
||||
.sidenote-checkbox:checked ~ & {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@include below-two-sidenotes {
|
||||
@include below-two-margins {
|
||||
.sidenote-content.sidenote-left {
|
||||
@include hidden-sidenote;
|
||||
margin-left: 0rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
left: -$sidenote-width/2
|
||||
}
|
||||
}
|
||||
|
||||
@include below-one-sidenote {
|
||||
.post-content {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@include below-one-margin {
|
||||
.sidenote-content.sidenote-right {
|
||||
@include hidden-sidenote;
|
||||
margin-right: 0rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: initial;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
@import "variables.scss";
|
||||
@import "mixins.scss";
|
||||
@import "margin.scss";
|
||||
@import "toc.scss";
|
||||
|
||||
body {
|
||||
font-family: $font-body;
|
||||
@@ -38,13 +40,22 @@ pre code {
|
||||
display: block;
|
||||
padding: 0.5rem;
|
||||
overflow-x: auto;
|
||||
background-color: $code-color;
|
||||
border: $code-border;
|
||||
}
|
||||
|
||||
div.highlight table pre {
|
||||
div.highlight table {
|
||||
border: $code-border !important;
|
||||
border-radius: 0px;
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
code {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
margin: auto;
|
||||
@@ -53,7 +64,17 @@ div.highlight table pre {
|
||||
box-sizing: border-box;
|
||||
|
||||
@include below-container-width {
|
||||
padding: 0rem 1rem 0rem 1rem;
|
||||
padding: 0 $container-min-padding 0 $container-min-padding;
|
||||
margin: 0;
|
||||
max-width: $container-width + 2 * $container-min-padding;
|
||||
}
|
||||
|
||||
@include below-two-margins {
|
||||
left: -($margin-width + $margin-inner-offset + $margin-outer-offset)/2;
|
||||
}
|
||||
|
||||
@include below-one-margin {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,8 +83,7 @@ div.highlight table pre {
|
||||
background-color: $primary-color;
|
||||
border: none;
|
||||
color: white;
|
||||
transition: color 0.25s;
|
||||
transition: background-color 0.25s;
|
||||
transition: color 0.25s, background-color 0.25s;
|
||||
text-align: left;
|
||||
|
||||
&:focus {
|
||||
|
||||
49
themes/vanilla/assets/scss/toc.scss
Normal file
49
themes/vanilla/assets/scss/toc.scss
Normal file
@@ -0,0 +1,49 @@
|
||||
@import "variables.scss";
|
||||
@import "mixins.scss";
|
||||
|
||||
$toc-color: $code-color;
|
||||
$toc-border-color: $code-border-color;
|
||||
|
||||
.table-of-contents {
|
||||
@include margin-content;
|
||||
@include margin-content-left;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: end;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
em {
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
#TableOfContents > ul {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding-left: 2rem;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
a {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.wrapper {
|
||||
@include bordered-block;
|
||||
padding: 1rem;
|
||||
background-color: $toc-color;
|
||||
border-color: $toc-border-color;
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
$container-width: 45rem;
|
||||
$container-min-padding: 1rem;
|
||||
$container-width-threshold: $container-width + 2 * $container-min-padding;
|
||||
$standard-border-width: .075rem;
|
||||
|
||||
$primary-color: #36e281;
|
||||
$primary-color-dark: darken($primary-color, 10%);
|
||||
$code-color: #f0f0f0;
|
||||
$code-color-dark: darken($code-color, 10%);
|
||||
$border-color: #bfbfbf;
|
||||
$code-color: #f0f0f0;
|
||||
$code-border-color: darken($code-color, 10%);
|
||||
|
||||
$font-heading: "Lora", serif;
|
||||
$font-body: "Raleway", serif;
|
||||
$font-code: "Inconsolata", monospace;
|
||||
|
||||
$standard-border: $standard-border-width solid $border-color;
|
||||
$code-border: $standard-border-width solid $code-border-color;
|
||||
|
||||
@@ -10,6 +10,14 @@
|
||||
</div>
|
||||
|
||||
<div class="post-content">
|
||||
{{ if not (eq .TableOfContents "<nav id=\"TableOfContents\"></nav>") }}
|
||||
<div class="table-of-contents">
|
||||
<div class="wrapper">
|
||||
<em>Table of Contents</em>
|
||||
{{ .TableOfContents }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ .Content }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
@@ -6,4 +6,4 @@
|
||||
{{ .Scratch.Set "u" $t }}
|
||||
{{ end }}
|
||||
{{ $v := first (add (sub (int (.Get 3)) (int (.Get 2))) 1) (.Scratch.Get "u") }}
|
||||
{{ highlight (delimit $v "\n") (.Get 0) "" }}
|
||||
{{ highlight (delimit $v "\n") (.Get 0) (printf "linenos=table,linenostart=%d" (.Get 2)) }}
|
||||
|
||||
Reference in New Issue
Block a user