Compare commits
6 Commits
d9486d08ae
...
1ffc43af98
Author | SHA1 | Date | |
---|---|---|---|
1ffc43af98 | |||
b27dc19e57 | |||
df0b819b0e | |||
21f90d85c5 | |||
18e3f2af55 | |||
3901c9b115 |
|
@ -97,10 +97,6 @@ type_ptr ast_case::typecheck(type_mgr& mgr, const type_env& env) const {
|
||||||
type_ptr case_type = mgr.resolve(of->typecheck(mgr, env), var);
|
type_ptr case_type = mgr.resolve(of->typecheck(mgr, env), var);
|
||||||
type_ptr branch_type = mgr.new_type();
|
type_ptr branch_type = mgr.new_type();
|
||||||
|
|
||||||
if(!dynamic_cast<type_base*>(case_type.get())) {
|
|
||||||
throw type_error("attempting case analysis of non-data type");
|
|
||||||
}
|
|
||||||
|
|
||||||
for(auto& branch : branches) {
|
for(auto& branch : branches) {
|
||||||
type_env new_env = env.scope();
|
type_env new_env = env.scope();
|
||||||
branch->pat->match(case_type, mgr, new_env);
|
branch->pat->match(case_type, mgr, new_env);
|
||||||
|
@ -108,6 +104,11 @@ type_ptr ast_case::typecheck(type_mgr& mgr, const type_env& env) const {
|
||||||
mgr.unify(branch_type, curr_branch_type);
|
mgr.unify(branch_type, curr_branch_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case_type = mgr.resolve(case_type, var);
|
||||||
|
if(!dynamic_cast<type_base*>(case_type.get())) {
|
||||||
|
throw type_error("attempting case analysis of non-data type");
|
||||||
|
}
|
||||||
|
|
||||||
return branch_type;
|
return branch_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ add_executable(compiler
|
||||||
type.cpp type.hpp
|
type.cpp type.hpp
|
||||||
error.cpp error.hpp
|
error.cpp error.hpp
|
||||||
binop.cpp binop.hpp
|
binop.cpp binop.hpp
|
||||||
|
instruction.cpp instruction.hpp
|
||||||
${BISON_parser_OUTPUTS}
|
${BISON_parser_OUTPUTS}
|
||||||
${FLEX_scanner_OUTPUTS}
|
${FLEX_scanner_OUTPUTS}
|
||||||
main.cpp
|
main.cpp
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include "error.hpp"
|
#include "error.hpp"
|
||||||
|
|
||||||
void print_indent(int n, std::ostream& to) {
|
static void print_indent(int n, std::ostream& to) {
|
||||||
while(n--) to << " ";
|
while(n--) to << " ";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +176,7 @@ void ast_case::resolve(const type_mgr& mgr) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ast_case::compile(const env_ptr& env, std::vector<instruction_ptr>& into) const {
|
void ast_case::compile(const env_ptr& env, std::vector<instruction_ptr>& into) const {
|
||||||
type_data* type = dynamic_cast<type_data*>(node_type.get());
|
type_data* type = dynamic_cast<type_data*>(of->node_type.get());
|
||||||
|
|
||||||
of->compile(env, into);
|
of->compile(env, into);
|
||||||
into.push_back(instruction_ptr(new instruction_eval()));
|
into.push_back(instruction_ptr(new instruction_eval()));
|
||||||
|
@ -201,9 +201,15 @@ void ast_case::compile(const env_ptr& env, std::vector<instruction_ptr>& into) c
|
||||||
}
|
}
|
||||||
jump_instruction->branches.push_back(std::move(branch_instructions));
|
jump_instruction->branches.push_back(std::move(branch_instructions));
|
||||||
} else if((cpat = dynamic_cast<pattern_constr*>(branch->pat.get()))) {
|
} 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));
|
||||||
|
}
|
||||||
|
|
||||||
branch_instructions.push_back(instruction_ptr(new instruction_split()));
|
branch_instructions.push_back(instruction_ptr(new instruction_split()));
|
||||||
branch->expr->compile(env_ptr(new env_offset(cpat->params.size(), env)),
|
branch->expr->compile(new_env, branch_instructions);
|
||||||
branch_instructions);
|
branch_instructions.push_back(instruction_ptr(new instruction_slide(
|
||||||
|
cpat->params.size())));
|
||||||
|
|
||||||
int new_tag = type->constructors[cpat->constr].tag;
|
int new_tag = type->constructors[cpat->constr].tag;
|
||||||
if(jump_instruction->tag_mappings.find(new_tag) !=
|
if(jump_instruction->tag_mappings.find(new_tag) !=
|
||||||
|
|
|
@ -58,7 +58,8 @@ struct definition {
|
||||||
|
|
||||||
virtual void typecheck_first(type_mgr& mgr, type_env& env) = 0;
|
virtual void typecheck_first(type_mgr& mgr, type_env& env) = 0;
|
||||||
virtual void typecheck_second(type_mgr& mgr, const type_env& env) const = 0;
|
virtual void typecheck_second(type_mgr& mgr, const type_env& env) const = 0;
|
||||||
virtual void resolve(const type_mgr& mgr) const = 0;
|
virtual void resolve(const type_mgr& mgr) = 0;
|
||||||
|
virtual void compile() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
using definition_ptr = std::unique_ptr<definition>;
|
using definition_ptr = std::unique_ptr<definition>;
|
||||||
|
@ -168,6 +169,8 @@ struct definition_defn : public definition {
|
||||||
type_ptr return_type;
|
type_ptr return_type;
|
||||||
std::vector<type_ptr> param_types;
|
std::vector<type_ptr> param_types;
|
||||||
|
|
||||||
|
std::vector<instruction_ptr> instructions;
|
||||||
|
|
||||||
definition_defn(std::string n, std::vector<std::string> p, ast_ptr b)
|
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)) {
|
: name(std::move(n)), params(std::move(p)), body(std::move(b)) {
|
||||||
|
|
||||||
|
@ -175,7 +178,8 @@ struct definition_defn : public definition {
|
||||||
|
|
||||||
void typecheck_first(type_mgr& mgr, type_env& env);
|
void typecheck_first(type_mgr& mgr, type_env& env);
|
||||||
void typecheck_second(type_mgr& mgr, const type_env& env) const;
|
void typecheck_second(type_mgr& mgr, const type_env& env) const;
|
||||||
void resolve(const type_mgr& mgr) const;
|
void resolve(const type_mgr& mgr);
|
||||||
|
void compile();
|
||||||
};
|
};
|
||||||
|
|
||||||
struct definition_data : public definition {
|
struct definition_data : public definition {
|
||||||
|
@ -187,5 +191,6 @@ struct definition_data : public definition {
|
||||||
|
|
||||||
void typecheck_first(type_mgr& mgr, type_env& env);
|
void typecheck_first(type_mgr& mgr, type_env& env);
|
||||||
void typecheck_second(type_mgr& mgr, const type_env& env) const;
|
void typecheck_second(type_mgr& mgr, const type_env& env) const;
|
||||||
void resolve(const type_mgr& mgr) const;
|
void resolve(const type_mgr& mgr);
|
||||||
|
void compile();
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "ast.hpp"
|
#include "ast.hpp"
|
||||||
|
#include "error.hpp"
|
||||||
|
|
||||||
void definition_defn::typecheck_first(type_mgr& mgr, type_env& env) {
|
void definition_defn::typecheck_first(type_mgr& mgr, type_env& env) {
|
||||||
return_type = mgr.new_type();
|
return_type = mgr.new_type();
|
||||||
|
@ -28,8 +29,25 @@ void definition_defn::typecheck_second(type_mgr& mgr, const type_env& env) const
|
||||||
mgr.unify(return_type, body_type);
|
mgr.unify(return_type, body_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
void definition_defn::resolve(const type_mgr& mgr) const {
|
void definition_defn::resolve(const type_mgr& mgr) {
|
||||||
|
type_var* var;
|
||||||
body->resolve_common(mgr);
|
body->resolve_common(mgr);
|
||||||
|
|
||||||
|
return_type = mgr.resolve(return_type, var);
|
||||||
|
if(var) throw type_error("ambiguously typed program");
|
||||||
|
for(auto& param_type : param_types) {
|
||||||
|
param_type = mgr.resolve(param_type, var);
|
||||||
|
if(var) throw type_error("ambiguously typed program");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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())));
|
||||||
}
|
}
|
||||||
|
|
||||||
void definition_data::typecheck_first(type_mgr& mgr, type_env& env) {
|
void definition_data::typecheck_first(type_mgr& mgr, type_env& env) {
|
||||||
|
@ -54,7 +72,10 @@ void definition_data::typecheck_second(type_mgr& mgr, const type_env& env) const
|
||||||
// Nothing
|
// Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
void definition_data::resolve(const type_mgr& mgr) const {
|
void definition_data::resolve(const type_mgr& mgr) {
|
||||||
// Nothing
|
// Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void definition_data::compile() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
78
code/compiler/06/instruction.cpp
Normal file
78
code/compiler/06/instruction.cpp
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
#include "instruction.hpp"
|
||||||
|
|
||||||
|
static void print_indent(int n, std::ostream& to) {
|
||||||
|
while(n--) to << " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
void instruction_pushint::print(int indent, std::ostream& to) const {
|
||||||
|
print_indent(indent, to);
|
||||||
|
to << "PushInt(" << value << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void instruction_pushglobal::print(int indent, std::ostream& to) const {
|
||||||
|
print_indent(indent, to);
|
||||||
|
to << "PushGlobal(" << name << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void instruction_push::print(int indent, std::ostream& to) const {
|
||||||
|
print_indent(indent, to);
|
||||||
|
to << "Push(" << offset << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void instruction_mkapp::print(int indent, std::ostream& to) const {
|
||||||
|
print_indent(indent, to);
|
||||||
|
to << "MkApp()" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void instruction_update::print(int indent, std::ostream& to) const {
|
||||||
|
print_indent(indent, to);
|
||||||
|
to << "Update(" << offset << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void instruction_pack::print(int indent, std::ostream& to) const {
|
||||||
|
print_indent(indent, to);
|
||||||
|
to << "Pack(" << tag << ", " << size << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void instruction_split::print(int indent, std::ostream& to) const {
|
||||||
|
print_indent(indent, to);
|
||||||
|
to << "Split()" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void instruction_jump::print(int indent, std::ostream& to) const {
|
||||||
|
print_indent(indent, to);
|
||||||
|
to << "Jump(" << std::endl;
|
||||||
|
for(auto& instruction_set : branches) {
|
||||||
|
for(auto& instruction : instruction_set) {
|
||||||
|
instruction->print(indent + 2, to);
|
||||||
|
}
|
||||||
|
to << std::endl;
|
||||||
|
}
|
||||||
|
print_indent(indent, to);
|
||||||
|
to << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void instruction_slide::print(int indent, std::ostream& to) const {
|
||||||
|
print_indent(indent, to);
|
||||||
|
to << "Slide(" << offset << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void instruction_binop::print(int indent, std::ostream& to) const {
|
||||||
|
print_indent(indent, to);
|
||||||
|
to << "BinOp(" << op_action(op) << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void instruction_eval::print(int indent, std::ostream& to) const {
|
||||||
|
print_indent(indent, to);
|
||||||
|
to << "Eval()" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void instruction_alloc::print(int indent, std::ostream& to) const {
|
||||||
|
print_indent(indent, to);
|
||||||
|
to << "Alloc(" << amount << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void instruction_unwind::print(int indent, std::ostream& to) const {
|
||||||
|
print_indent(indent, to);
|
||||||
|
to << "Unwind()" << std::endl;
|
||||||
|
}
|
|
@ -1,12 +1,15 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "binop.hpp"
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <ostream>
|
||||||
|
#include "binop.hpp"
|
||||||
|
|
||||||
struct instruction {
|
struct instruction {
|
||||||
virtual ~instruction() = default;
|
virtual ~instruction() = default;
|
||||||
|
|
||||||
|
virtual void print(int indent, std::ostream& to) const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
using instruction_ptr = std::unique_ptr<instruction>;
|
using instruction_ptr = std::unique_ptr<instruction>;
|
||||||
|
@ -16,6 +19,8 @@ struct instruction_pushint : public instruction {
|
||||||
|
|
||||||
instruction_pushint(int v)
|
instruction_pushint(int v)
|
||||||
: value(v) {}
|
: value(v) {}
|
||||||
|
|
||||||
|
void print(int indent, std::ostream& to) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct instruction_pushglobal : public instruction {
|
struct instruction_pushglobal : public instruction {
|
||||||
|
@ -23,6 +28,8 @@ struct instruction_pushglobal : public instruction {
|
||||||
|
|
||||||
instruction_pushglobal(std::string n)
|
instruction_pushglobal(std::string n)
|
||||||
: name(std::move(n)) {}
|
: name(std::move(n)) {}
|
||||||
|
|
||||||
|
void print(int indent, std::ostream& to) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct instruction_push : public instruction {
|
struct instruction_push : public instruction {
|
||||||
|
@ -30,10 +37,12 @@ struct instruction_push : public instruction {
|
||||||
|
|
||||||
instruction_push(int o)
|
instruction_push(int o)
|
||||||
: offset(o) {}
|
: offset(o) {}
|
||||||
|
|
||||||
|
void print(int indent, std::ostream& to) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct instruction_mkapp : public instruction {
|
struct instruction_mkapp : public instruction {
|
||||||
|
void print(int indent, std::ostream& to) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct instruction_update : public instruction {
|
struct instruction_update : public instruction {
|
||||||
|
@ -41,6 +50,8 @@ struct instruction_update : public instruction {
|
||||||
|
|
||||||
instruction_update(int o)
|
instruction_update(int o)
|
||||||
: offset(o) {}
|
: offset(o) {}
|
||||||
|
|
||||||
|
void print(int indent, std::ostream& to) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct instruction_pack : public instruction {
|
struct instruction_pack : public instruction {
|
||||||
|
@ -49,15 +60,19 @@ struct instruction_pack : public instruction {
|
||||||
|
|
||||||
instruction_pack(int t, int s)
|
instruction_pack(int t, int s)
|
||||||
: tag(t), size(s) {}
|
: tag(t), size(s) {}
|
||||||
|
|
||||||
|
void print(int indent, std::ostream& to) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct instruction_split : public instruction {
|
struct instruction_split : public instruction {
|
||||||
|
void print(int indent, std::ostream& to) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct instruction_jump : public instruction {
|
struct instruction_jump : public instruction {
|
||||||
std::vector<std::vector<instruction_ptr>> branches;
|
std::vector<std::vector<instruction_ptr>> branches;
|
||||||
std::map<int, int> tag_mappings;
|
std::map<int, int> tag_mappings;
|
||||||
|
|
||||||
|
void print(int indent, std::ostream& to) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct instruction_slide : public instruction {
|
struct instruction_slide : public instruction {
|
||||||
|
@ -65,6 +80,8 @@ struct instruction_slide : public instruction {
|
||||||
|
|
||||||
instruction_slide(int o)
|
instruction_slide(int o)
|
||||||
: offset(o) {}
|
: offset(o) {}
|
||||||
|
|
||||||
|
void print(int indent, std::ostream& to) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct instruction_binop : public instruction {
|
struct instruction_binop : public instruction {
|
||||||
|
@ -72,10 +89,12 @@ struct instruction_binop : public instruction {
|
||||||
|
|
||||||
instruction_binop(binop o)
|
instruction_binop(binop o)
|
||||||
: op(o) {}
|
: op(o) {}
|
||||||
|
|
||||||
|
void print(int indent, std::ostream& to) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct instruction_eval : public instruction {
|
struct instruction_eval : public instruction {
|
||||||
|
void print(int indent, std::ostream& to) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct instruction_alloc : public instruction {
|
struct instruction_alloc : public instruction {
|
||||||
|
@ -83,8 +102,10 @@ struct instruction_alloc : public instruction {
|
||||||
|
|
||||||
instruction_alloc(int a)
|
instruction_alloc(int a)
|
||||||
: amount(a) {}
|
: amount(a) {}
|
||||||
|
|
||||||
|
void print(int indent, std::ostream& to) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct instruction_unwind : public instruction {
|
struct instruction_unwind : public instruction {
|
||||||
|
void print(int indent, std::ostream& to) const;
|
||||||
};
|
};
|
||||||
|
|
|
@ -42,6 +42,19 @@ void typecheck_program(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void compile_program(const std::vector<definition_ptr>& prog) {
|
||||||
|
for(auto& def : prog) {
|
||||||
|
def->compile();
|
||||||
|
|
||||||
|
definition_defn* defn = dynamic_cast<definition_defn*>(def.get());
|
||||||
|
if(!defn) continue;
|
||||||
|
for(auto& instruction : defn->instructions) {
|
||||||
|
instruction->print(0, std::cout);
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
yy::parser parser;
|
yy::parser parser;
|
||||||
type_mgr mgr;
|
type_mgr mgr;
|
||||||
|
@ -60,6 +73,7 @@ int main() {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
typecheck_program(program, mgr, env);
|
typecheck_program(program, mgr, env);
|
||||||
|
compile_program(program);
|
||||||
} catch(unification_error& err) {
|
} catch(unification_error& err) {
|
||||||
std::cout << "failed to unify types: " << std::endl;
|
std::cout << "failed to unify types: " << std::endl;
|
||||||
std::cout << " (1) \033[34m";
|
std::cout << " (1) \033[34m";
|
||||||
|
|
|
@ -138,3 +138,4 @@ Here are the posts that I've written so far for this series:
|
||||||
* [Typechecking]({{< relref "03_compiler_typechecking.md" >}})
|
* [Typechecking]({{< relref "03_compiler_typechecking.md" >}})
|
||||||
* [Small Improvements]({{< relref "04_compiler_improvements.md" >}})
|
* [Small Improvements]({{< relref "04_compiler_improvements.md" >}})
|
||||||
* [Execution]({{< relref "05_compiler_execution.md" >}})
|
* [Execution]({{< relref "05_compiler_execution.md" >}})
|
||||||
|
* [Compilation]({{< relref "06_compiler_semantics.md" >}})
|
||||||
|
|
|
@ -65,8 +65,8 @@ Finally, just like `ast_case::typecheck` called
|
||||||
|
|
||||||
We follow the same implementation strategy for patterns,
|
We follow the same implementation strategy for patterns,
|
||||||
but we don't need indentation, or recursion:
|
but we don't need indentation, or recursion:
|
||||||
{{< codelines "C++" "compiler/04/ast.cpp" 114 116 >}}
|
{{< codelines "C++" "compiler/04/ast.cpp" 115 117 >}}
|
||||||
{{< codelines "C++" "compiler/04/ast.cpp" 122 127 >}}
|
{{< codelines "C++" "compiler/04/ast.cpp" 123 128 >}}
|
||||||
|
|
||||||
In `main`, let's print the bodies of each function we receive from the parser:
|
In `main`, let's print the bodies of each function we receive from the parser:
|
||||||
{{< codelines "C++" "compiler/04/main.cpp" 47 56 >}}
|
{{< codelines "C++" "compiler/04/main.cpp" 47 56 >}}
|
||||||
|
@ -160,12 +160,12 @@ we simply pass the type of the expression to be case analyzed into
|
||||||
the pattern matching method. However, since we don't want
|
the pattern matching method. However, since we don't want
|
||||||
case analysis on functions, we ensure that the type of the expression
|
case analysis on functions, we ensure that the type of the expression
|
||||||
is `type_base`. If not, we report this:
|
is `type_base`. If not, we report this:
|
||||||
{{< codelines "C++" "compiler/04/ast.cpp" 100 102 >}}
|
{{< codelines "C++" "compiler/04/ast.cpp" 107 110 >}}
|
||||||
|
|
||||||
The next exception is in `pattern_constr::match`. It occurs
|
The next exception is in `pattern_constr::match`. It occurs
|
||||||
when the pattern has a constructor we don't recognize, and
|
when the pattern has a constructor we don't recognize, and
|
||||||
that's exactly what we report:
|
that's exactly what we report:
|
||||||
{{< codelines "C++" "compiler/04/ast.cpp" 131 133 >}}
|
{{< codelines "C++" "compiler/04/ast.cpp" 132 134 >}}
|
||||||
|
|
||||||
The next exception occurs in a loop, when we bind
|
The next exception occurs in a loop, when we bind
|
||||||
types for each of the constructor pattern's variables.
|
types for each of the constructor pattern's variables.
|
||||||
|
@ -174,7 +174,7 @@ constructor type to a `type_arr`. Conceptually,
|
||||||
this means that the pattern wants to apply the
|
this means that the pattern wants to apply the
|
||||||
constructor to more parameters than it actually
|
constructor to more parameters than it actually
|
||||||
takes:
|
takes:
|
||||||
{{< codelines "C++" "compiler/04/ast.cpp" 137 137 >}}
|
{{< codelines "C++" "compiler/04/ast.cpp" 138 138 >}}
|
||||||
|
|
||||||
We remove the last throw at the bottom of `pattern_constr::match`.
|
We remove the last throw at the bottom of `pattern_constr::match`.
|
||||||
This is because once unification succeeds, we know
|
This is because once unification succeeds, we know
|
||||||
|
|
|
@ -592,4 +592,4 @@ tell us what to do with our `ast` structs. We'll need to define
|
||||||
rules to translate trees into these instructions, and I've already
|
rules to translate trees into these instructions, and I've already
|
||||||
alluded to this when we went over `double 326`.
|
alluded to this when we went over `double 326`.
|
||||||
However, this has already gotten pretty long,
|
However, this has already gotten pretty long,
|
||||||
so we'll do it in the next post: (link me!)
|
so we'll do it in the next post: [Part 6 - Compilation]({{< relref "06_compiler_semantics.md" >}}).
|
||||||
|
|
|
@ -168,6 +168,10 @@ first define C++ structs for the instructions of the G-machine:
|
||||||
|
|
||||||
{{< codeblock "C++" "compiler/06/instruction.hpp" >}}
|
{{< codeblock "C++" "compiler/06/instruction.hpp" >}}
|
||||||
|
|
||||||
|
I omit the implementation of the various (trivial) `print` methods in this post;
|
||||||
|
as always, you can look at the full project source code, which is
|
||||||
|
freely available for each post in the series.
|
||||||
|
|
||||||
We can now envision a method on the `ast` struct that takes an environment
|
We can now envision a method on the `ast` struct that takes an environment
|
||||||
(just like our compilation scheme takes the environment \\(\\rho\\\)),
|
(just like our compilation scheme takes the environment \\(\\rho\\\)),
|
||||||
and compiles the `ast`. Rather than returning a vector
|
and compiles the `ast`. Rather than returning a vector
|
||||||
|
@ -282,7 +286,7 @@ struct, called `type_data`:
|
||||||
|
|
||||||
When we create types from `definition_data`, we tag the corresponding constructors:
|
When we create types from `definition_data`, we tag the corresponding constructors:
|
||||||
|
|
||||||
{{< codelines "C++" "compiler/06/definition.cpp" 35 51 >}}
|
{{< codelines "C++" "compiler/06/definition.cpp" 53 69 >}}
|
||||||
|
|
||||||
Ah, but adding constructor info to the type doesn't solve the problem.
|
Ah, but adding constructor info to the type doesn't solve the problem.
|
||||||
Once we performed type checking, we don't keep
|
Once we performed type checking, we don't keep
|
||||||
|
@ -339,7 +343,7 @@ of a node. Here's a sample implementation from `ast_binop`:
|
||||||
|
|
||||||
And here's the implementation of `definition::resolve` on `definition_defn`:
|
And here's the implementation of `definition::resolve` on `definition_defn`:
|
||||||
|
|
||||||
{{< codelines "C++" "compiler/06/definition.cpp" 31 33 >}}
|
{{< codelines "C++" "compiler/06/definition.cpp" 32 42 >}}
|
||||||
|
|
||||||
Finally, we call `resolve` at the end `typecheck_program` in `main.cpp`:
|
Finally, we call `resolve` at the end `typecheck_program` in `main.cpp`:
|
||||||
|
|
||||||
|
@ -348,7 +352,7 @@ Finally, we call `resolve` at the end `typecheck_program` in `main.cpp`:
|
||||||
At last, we're ready to implement the code for compiling `ast_case`.
|
At last, we're ready to implement the code for compiling `ast_case`.
|
||||||
Here it is, in all its glory:
|
Here it is, in all its glory:
|
||||||
|
|
||||||
{{< codelines "C++" "compiler/06/ast.cpp" 178 224 >}}
|
{{< codelines "C++" "compiler/06/ast.cpp" 178 230 >}}
|
||||||
|
|
||||||
There's a lot to unpack here. First of all, just like we said in the compilation
|
There's a lot to unpack here. First of all, just like we said in the compilation
|
||||||
scheme, we want to build and evaluate the expression that's being analyzed.
|
scheme, we want to build and evaluate the expression that's being analyzed.
|
||||||
|
@ -399,4 +403,97 @@ After we're done with all the branches, we also check for non-exhaustive pattern
|
||||||
since otherwise we could run into runtime errors. With this, the case expression,
|
since otherwise we could run into runtime errors. With this, the case expression,
|
||||||
and the last of the AST nodes, can be compiled.
|
and the last of the AST nodes, can be compiled.
|
||||||
|
|
||||||
{{< todo >}}Backport bugfix in case's typecheck{{< /todo >}}
|
We also add a `compile` method to definitions, since they contain
|
||||||
|
our AST nodes. The method is empty for `defn_data`, and
|
||||||
|
looks as follows for `definition_defn`:
|
||||||
|
|
||||||
|
{{< codelines "C++" "compiler/06/definition.cpp" 44 51 >}}
|
||||||
|
|
||||||
|
Notice that we terminate the function with Update. This
|
||||||
|
will turn the `ast_app` node that served as the "root"
|
||||||
|
of the application into an indirection to the value that we have computed.
|
||||||
|
In essense, this is how we can lazily evaluate expressions.
|
||||||
|
|
||||||
|
Finally, we make a function in our `main.cpp` file to compile
|
||||||
|
all the definitions:
|
||||||
|
|
||||||
|
{{< codelines "C++" "compiler/06/main.cpp" 45 56 >}}
|
||||||
|
|
||||||
|
In this method, we also include some extra
|
||||||
|
output to help us see the result of our compilation. Since
|
||||||
|
at the moment, only the `definition_defn` program has to
|
||||||
|
be compiled, we try cast all definitions to it, and if
|
||||||
|
we succeed, we print them out.
|
||||||
|
|
||||||
|
Let's try it all out! For the below sample program:
|
||||||
|
|
||||||
|
{{< rawblock "compiler/06/examples/works1.txt" >}}
|
||||||
|
|
||||||
|
Our compiler produces the following new output:
|
||||||
|
```
|
||||||
|
PushInt(6)
|
||||||
|
PushInt(320)
|
||||||
|
PushGlobal(plus)
|
||||||
|
MkApp()
|
||||||
|
MkApp()
|
||||||
|
|
||||||
|
Push(1)
|
||||||
|
Push(1)
|
||||||
|
PushGlobal(+)
|
||||||
|
MkApp()
|
||||||
|
MkApp()
|
||||||
|
```
|
||||||
|
|
||||||
|
The first sequence of instructions is clearly `main`. It creates
|
||||||
|
an application of `plus` to `320`, and then applies that to
|
||||||
|
`6`, which results in `plus 320 6`, which is correct. The
|
||||||
|
second sequence of instruction pushes the parameter that
|
||||||
|
sits on offset 1 from the top of the stack (`y`). It then
|
||||||
|
pushes a parameter from the same offset again, but this time,
|
||||||
|
since `y` was previously pushed on the stack, `x` is now
|
||||||
|
in that position, so `x` is pushed onto the stack.
|
||||||
|
Finally, `+` is pushed, and the application
|
||||||
|
`(+) x y` is created, which is equivalent to `x+y`.
|
||||||
|
|
||||||
|
Let's also take a look at a case expression program:
|
||||||
|
|
||||||
|
{{< rawblock "compiler/06/examples/works3.txt" >}}
|
||||||
|
|
||||||
|
The result of the compilation is as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
Push(0)
|
||||||
|
Eval()
|
||||||
|
Jump(
|
||||||
|
Split()
|
||||||
|
PushInt(0)
|
||||||
|
Slide(0)
|
||||||
|
|
||||||
|
Split()
|
||||||
|
Push(1)
|
||||||
|
PushGlobal(length)
|
||||||
|
MkApp()
|
||||||
|
PushInt(1)
|
||||||
|
PushGlobal(+)
|
||||||
|
MkApp()
|
||||||
|
MkApp()
|
||||||
|
Slide(2)
|
||||||
|
|
||||||
|
)
|
||||||
|
Update(1)
|
||||||
|
```
|
||||||
|
|
||||||
|
We push the first (and only) parameter onto the stack. We then make
|
||||||
|
sure it's evaluated, and perform case analysis: if the list
|
||||||
|
is `Nil`, we simply push the number 0 onto the stack. If it's
|
||||||
|
a concatenation of some `x` and another lists `xs`, we
|
||||||
|
push `xs` and `length` onto the stack, make the application
|
||||||
|
(`length xs`), push the 1, and finally apply `+` to the result.
|
||||||
|
This all makes sense!
|
||||||
|
|
||||||
|
With this, we've been able to compile our expressions and functions
|
||||||
|
into G-machine code. We're not done, however - our computers
|
||||||
|
aren't G-machines. We'll need to compile our G-machine code to
|
||||||
|
__machine code__ (we will use LLVM for this), implement the
|
||||||
|
__runtime__, and develop a __garbage collector__. We'll
|
||||||
|
tackle the first of these in the next post - see you there!
|
||||||
|
|
Loading…
Reference in New Issue
Block a user