This commit is contained in:
parent
1bdb4a650e
commit
e3834ed6ea
|
@ -26,8 +26,8 @@ class function_graph {
|
||||||
size_t indegree;
|
size_t indegree;
|
||||||
};
|
};
|
||||||
|
|
||||||
using edge = std::pair<function, function>;
|
|
||||||
using data_ptr = std::shared_ptr<group_data>;
|
using data_ptr = std::shared_ptr<group_data>;
|
||||||
|
using edge = std::pair<function, function>;
|
||||||
using group_edge = std::pair<group_id, group_id>;
|
using group_edge = std::pair<group_id, group_id>;
|
||||||
|
|
||||||
std::map<function, std::set<function>> adjacency_lists;
|
std::map<function, std::set<function>> adjacency_lists;
|
||||||
|
|
|
@ -203,7 +203,7 @@ defn testOne = { if True False True }
|
||||||
defn testTwo = { if True 0 1 }
|
defn testTwo = { if True 0 1 }
|
||||||
```
|
```
|
||||||
|
|
||||||
If we go through and type check them top-to-bottom, the following happens:
|
If we go through and typecheck them top-to-bottom, the following happens:
|
||||||
|
|
||||||
1. We start by assuming \\(\\text{if} : a \\rightarrow b \\rightarrow c \\rightarrow d\\),
|
1. We start by assuming \\(\\text{if} : a \\rightarrow b \\rightarrow c \\rightarrow d\\),
|
||||||
\\(\\text{testOne} : e\\) and \\(\\text{testTwo} : f\\).
|
\\(\\text{testOne} : e\\) and \\(\\text{testTwo} : f\\).
|
||||||
|
@ -343,3 +343,83 @@ includes the group's adjacency list and
|
||||||
(both used for Kahn's topological sorting algorithm), as well as the set
|
(both used for Kahn's topological sorting algorithm), as well as the set
|
||||||
of functions in the group (which we will eventually return).
|
of functions in the group (which we will eventually return).
|
||||||
|
|
||||||
|
The `adjacency_lists` and `edges` fields are the meat of the graph representation.
|
||||||
|
Both of the variables provide a different view of the same graph: `adjacency_lists`
|
||||||
|
associates with every function a list of functions it depends on, while
|
||||||
|
`edges` holds a set of tuples describing edges in the graph. Having
|
||||||
|
more than one representation makes it more convenient for us to perform
|
||||||
|
different operations on our graphs.
|
||||||
|
|
||||||
|
Next up are some internal methods that perform the various steps we described
|
||||||
|
above:
|
||||||
|
* `compute_transitive_edges` applies Warshall's algorithm to find the graph's
|
||||||
|
transitive closure.
|
||||||
|
* `create_groups` creates two mappings, one from functions to their respective
|
||||||
|
groups' IDs, and one from group IDs to information about the corresponding groups.
|
||||||
|
This step is largely used to determine which functions belong to the same group,
|
||||||
|
and as such, uses the set of transitive edges generated by `compute_transitive_edges`.
|
||||||
|
* `create_edges` creates edges __between groups__. During this step, the indegrees
|
||||||
|
of each group are computed, as well as their adjacency lists.
|
||||||
|
* `generate_order` uses the indegrees and adjacency lists produced in the prior step
|
||||||
|
to establish a topological order.
|
||||||
|
|
||||||
|
Finally, the `add_edge` method is used to add a new dependency between two functions,
|
||||||
|
while the `compute_order` method uses the internal methods described above to convert
|
||||||
|
the function dependency graph into a properly ordered list of groups.
|
||||||
|
|
||||||
|
Let's start by looking at how to implement the internal methods. `compute_transitive_edges`
|
||||||
|
is a very straightforward implementation of Warshall's:
|
||||||
|
|
||||||
|
{{< codelines "C++" "compiler/10/graph.hpp" 53 71 >}}
|
||||||
|
|
||||||
|
Next is `create_groups`, for each function, we iterate over all other functions.
|
||||||
|
If the other function is mutually dependent with the first function, we add
|
||||||
|
it to the same group. In the outer loop, we skip over functions that have
|
||||||
|
already been added to the group. This is because
|
||||||
|
{{< sidenote "right" "equivalence-note" "mutual dependence" >}}
|
||||||
|
There is actually a slight difference between "mutual dependence"
|
||||||
|
the way we defined it and "being in the same group", and
|
||||||
|
it lies in the symmetric property of an equivalence relation.
|
||||||
|
We defined a function to depend on another function if it calls
|
||||||
|
that other function. Then, a recursive function depends on itself,
|
||||||
|
but a non-recursive function does not, and therefore does not
|
||||||
|
satisfy the symmetric property. However, as far as we're concerned,
|
||||||
|
a function should be in a group with itself even if it's not recursive. Thus, the
|
||||||
|
real equivalence relation we use is "in the same group as", and
|
||||||
|
consists of "mutual dependence" extended with symmetry.
|
||||||
|
{{< /sidenote >}}
|
||||||
|
is an [equivalence relation](https://en.wikipedia.org/wiki/Equivalence_relation),
|
||||||
|
which means that if we already added a function to a group, all its
|
||||||
|
group members were also already visited and added.
|
||||||
|
|
||||||
|
{{< codelines "C++" "compiler/10/graph.hpp" 73 94 >}}
|
||||||
|
|
||||||
|
Once groups have been created, we use their functions' edges
|
||||||
|
to create edges for the groups themselves, using `create_edges`.
|
||||||
|
We avoid creating edges from a group to itself, to avoid
|
||||||
|
unnecessary cycles. While constructing the edges, we also
|
||||||
|
increment the relevant indegree counter.
|
||||||
|
|
||||||
|
{{< codelines "C++" "compiler/10/graph.hpp" 96 113 >}}
|
||||||
|
|
||||||
|
Finally, we apply Kahn's algorithm to create a topological order
|
||||||
|
in `generate_order`:
|
||||||
|
|
||||||
|
{{< codelines "C++" "compiler/10/graph.hpp" 115 140 >}}
|
||||||
|
|
||||||
|
These four steps are used in `compute_order`:
|
||||||
|
|
||||||
|
{{< codelines "C++" "compiler/10/graph.hpp" 152 160 >}}
|
||||||
|
|
||||||
|
Finally, `add_edge` straightforwardly adds an edge
|
||||||
|
to the graph:
|
||||||
|
|
||||||
|
{{< codelines "C++" "compiler/10/graph.hpp" 142 150 >}}
|
||||||
|
|
||||||
|
With this, we can now properly order our typechecking.
|
||||||
|
However, there are a few pieces of the puzzle missing.
|
||||||
|
First of all, we need to actually insert function
|
||||||
|
dependencies into the graph. Second, we need to think
|
||||||
|
about how our existing language features and implementation
|
||||||
|
will interact with polymorphism. Third, we have to come up
|
||||||
|
with an implementation of polymorphic data types.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user