Add finishing touches to code for part 6 of compiler series
This commit is contained in:
parent
18e3f2af55
commit
21f90d85c5
|
@ -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) !=
|
||||||
|
|
|
@ -59,6 +59,7 @@ 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) = 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)) {
|
||||||
|
|
||||||
|
@ -176,6 +179,7 @@ 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);
|
void resolve(const type_mgr& mgr);
|
||||||
|
void compile();
|
||||||
};
|
};
|
||||||
|
|
||||||
struct definition_data : public definition {
|
struct definition_data : public definition {
|
||||||
|
@ -188,4 +192,5 @@ 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);
|
void resolve(const type_mgr& mgr);
|
||||||
|
void compile();
|
||||||
};
|
};
|
||||||
|
|
|
@ -41,6 +41,15 @@ void definition_defn::resolve(const type_mgr& mgr) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
type_data* this_type = new type_data(name);
|
type_data* this_type = new type_data(name);
|
||||||
type_ptr return_type = type_ptr(this_type);
|
type_ptr return_type = type_ptr(this_type);
|
||||||
|
@ -67,3 +76,6 @@ void definition_data::resolve(const type_mgr& mgr) {
|
||||||
// Nothing
|
// Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void definition_data::compile() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -21,12 +21,12 @@ void instruction_push::print(int indent, std::ostream& to) const {
|
||||||
|
|
||||||
void instruction_mkapp::print(int indent, std::ostream& to) const {
|
void instruction_mkapp::print(int indent, std::ostream& to) const {
|
||||||
print_indent(indent, to);
|
print_indent(indent, to);
|
||||||
to << "Push()" << std::endl;
|
to << "MkApp()" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
void instruction_update::print(int indent, std::ostream& to) const {
|
void instruction_update::print(int indent, std::ostream& to) const {
|
||||||
print_indent(indent, to);
|
print_indent(indent, to);
|
||||||
to << "Offset(" << offset << ")" << std::endl;
|
to << "Update(" << offset << ")" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
void instruction_pack::print(int indent, std::ostream& to) const {
|
void instruction_pack::print(int indent, std::ostream& to) const {
|
||||||
|
@ -48,6 +48,8 @@ void instruction_jump::print(int indent, std::ostream& to) const {
|
||||||
}
|
}
|
||||||
to << std::endl;
|
to << std::endl;
|
||||||
}
|
}
|
||||||
|
print_indent(indent, to);
|
||||||
|
to << ")" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
void instruction_slide::print(int indent, std::ostream& to) const {
|
void instruction_slide::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";
|
||||||
|
|
|
@ -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,51 @@ 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.
|
||||||
|
|
||||||
|
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 >}}
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
{{< todo >}}Backport bugfix in case's typecheck{{< /todo >}}
|
{{< todo >}}Backport bugfix in case's typecheck{{< /todo >}}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user