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 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) {
|
||||
type_env new_env = env.scope();
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ add_executable(compiler
|
|||
type.cpp type.hpp
|
||||
error.cpp error.hpp
|
||||
binop.cpp binop.hpp
|
||||
instruction.cpp instruction.hpp
|
||||
${BISON_parser_OUTPUTS}
|
||||
${FLEX_scanner_OUTPUTS}
|
||||
main.cpp
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#include <ostream>
|
||||
#include "error.hpp"
|
||||
|
||||
void print_indent(int n, std::ostream& to) {
|
||||
static void print_indent(int n, std::ostream& 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 {
|
||||
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);
|
||||
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));
|
||||
} 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->expr->compile(env_ptr(new env_offset(cpat->params.size(), env)),
|
||||
branch_instructions);
|
||||
branch->expr->compile(new_env, branch_instructions);
|
||||
branch_instructions.push_back(instruction_ptr(new instruction_slide(
|
||||
cpat->params.size())));
|
||||
|
||||
int new_tag = type->constructors[cpat->constr].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_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>;
|
||||
|
@ -168,6 +169,8 @@ struct definition_defn : public definition {
|
|||
type_ptr return_type;
|
||||
std::vector<type_ptr> param_types;
|
||||
|
||||
std::vector<instruction_ptr> instructions;
|
||||
|
||||
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)) {
|
||||
|
||||
|
@ -175,7 +178,8 @@ struct definition_defn : public definition {
|
|||
|
||||
void typecheck_first(type_mgr& mgr, type_env& env);
|
||||
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 {
|
||||
|
@ -187,5 +191,6 @@ struct definition_data : public definition {
|
|||
|
||||
void typecheck_first(type_mgr& mgr, type_env& env);
|
||||
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 "error.hpp"
|
||||
|
||||
void definition_defn::typecheck_first(type_mgr& mgr, type_env& env) {
|
||||
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);
|
||||
}
|
||||
|
||||
void definition_defn::resolve(const type_mgr& mgr) const {
|
||||
void definition_defn::resolve(const type_mgr& mgr) {
|
||||
type_var* var;
|
||||
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) {
|
||||
|
@ -54,7 +72,10 @@ void definition_data::typecheck_second(type_mgr& mgr, const type_env& env) const
|
|||
// Nothing
|
||||
}
|
||||
|
||||
void definition_data::resolve(const type_mgr& mgr) const {
|
||||
void definition_data::resolve(const type_mgr& mgr) {
|
||||
// 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
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "binop.hpp"
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <ostream>
|
||||
#include "binop.hpp"
|
||||
|
||||
struct instruction {
|
||||
virtual ~instruction() = default;
|
||||
|
||||
virtual void print(int indent, std::ostream& to) const = 0;
|
||||
};
|
||||
|
||||
using instruction_ptr = std::unique_ptr<instruction>;
|
||||
|
@ -16,6 +19,8 @@ struct instruction_pushint : public instruction {
|
|||
|
||||
instruction_pushint(int v)
|
||||
: value(v) {}
|
||||
|
||||
void print(int indent, std::ostream& to) const;
|
||||
};
|
||||
|
||||
struct instruction_pushglobal : public instruction {
|
||||
|
@ -23,6 +28,8 @@ struct instruction_pushglobal : public instruction {
|
|||
|
||||
instruction_pushglobal(std::string n)
|
||||
: name(std::move(n)) {}
|
||||
|
||||
void print(int indent, std::ostream& to) const;
|
||||
};
|
||||
|
||||
struct instruction_push : public instruction {
|
||||
|
@ -30,10 +37,12 @@ struct instruction_push : public instruction {
|
|||
|
||||
instruction_push(int o)
|
||||
: offset(o) {}
|
||||
|
||||
void print(int indent, std::ostream& to) const;
|
||||
};
|
||||
|
||||
struct instruction_mkapp : public instruction {
|
||||
|
||||
void print(int indent, std::ostream& to) const;
|
||||
};
|
||||
|
||||
struct instruction_update : public instruction {
|
||||
|
@ -41,6 +50,8 @@ struct instruction_update : public instruction {
|
|||
|
||||
instruction_update(int o)
|
||||
: offset(o) {}
|
||||
|
||||
void print(int indent, std::ostream& to) const;
|
||||
};
|
||||
|
||||
struct instruction_pack : public instruction {
|
||||
|
@ -49,15 +60,19 @@ struct instruction_pack : public instruction {
|
|||
|
||||
instruction_pack(int t, int s)
|
||||
: tag(t), size(s) {}
|
||||
|
||||
void print(int indent, std::ostream& to) const;
|
||||
};
|
||||
|
||||
struct instruction_split : public instruction {
|
||||
|
||||
void print(int indent, std::ostream& to) const;
|
||||
};
|
||||
|
||||
struct instruction_jump : public instruction {
|
||||
std::vector<std::vector<instruction_ptr>> branches;
|
||||
std::map<int, int> tag_mappings;
|
||||
|
||||
void print(int indent, std::ostream& to) const;
|
||||
};
|
||||
|
||||
struct instruction_slide : public instruction {
|
||||
|
@ -65,6 +80,8 @@ struct instruction_slide : public instruction {
|
|||
|
||||
instruction_slide(int o)
|
||||
: offset(o) {}
|
||||
|
||||
void print(int indent, std::ostream& to) const;
|
||||
};
|
||||
|
||||
struct instruction_binop : public instruction {
|
||||
|
@ -72,10 +89,12 @@ struct instruction_binop : public instruction {
|
|||
|
||||
instruction_binop(binop o)
|
||||
: op(o) {}
|
||||
|
||||
void print(int indent, std::ostream& to) const;
|
||||
};
|
||||
|
||||
struct instruction_eval : public instruction {
|
||||
|
||||
void print(int indent, std::ostream& to) const;
|
||||
};
|
||||
|
||||
struct instruction_alloc : public instruction {
|
||||
|
@ -83,8 +102,10 @@ struct instruction_alloc : public instruction {
|
|||
|
||||
instruction_alloc(int a)
|
||||
: amount(a) {}
|
||||
|
||||
void print(int indent, std::ostream& to) const;
|
||||
};
|
||||
|
||||
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() {
|
||||
yy::parser parser;
|
||||
type_mgr mgr;
|
||||
|
@ -60,6 +73,7 @@ int main() {
|
|||
}
|
||||
try {
|
||||
typecheck_program(program, mgr, env);
|
||||
compile_program(program);
|
||||
} catch(unification_error& err) {
|
||||
std::cout << "failed to unify types: " << std::endl;
|
||||
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" >}})
|
||||
* [Small Improvements]({{< relref "04_compiler_improvements.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,
|
||||
but we don't need indentation, or recursion:
|
||||
{{< codelines "C++" "compiler/04/ast.cpp" 114 116 >}}
|
||||
{{< codelines "C++" "compiler/04/ast.cpp" 122 127 >}}
|
||||
{{< codelines "C++" "compiler/04/ast.cpp" 115 117 >}}
|
||||
{{< codelines "C++" "compiler/04/ast.cpp" 123 128 >}}
|
||||
|
||||
In `main`, let's print the bodies of each function we receive from the parser:
|
||||
{{< 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
|
||||
case analysis on functions, we ensure that the type of the expression
|
||||
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
|
||||
when the pattern has a constructor we don't recognize, and
|
||||
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
|
||||
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
|
||||
constructor to more parameters than it actually
|
||||
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`.
|
||||
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
|
||||
alluded to this when we went over `double 326`.
|
||||
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" >}}
|
||||
|
||||
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
|
||||
(just like our compilation scheme takes the environment \\(\\rho\\\)),
|
||||
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:
|
||||
|
||||
{{< 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.
|
||||
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`:
|
||||
|
||||
{{< 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`:
|
||||
|
||||
|
@ -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`.
|
||||
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
|
||||
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,
|
||||
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