Today work has centered around finishing up the draft implementation of the demo of the ctest extension in Idris. Like yesterday, this combined both of my current endeavors; Idris and the ctest extension to support modules.
Today we got a lot of work done; I finished the entire implementation and patched up some holes I had in my reasoning of the module resolution algorithm. There have been a few changes to the algorithms, but the core idea is the same.
From a high-level perspective, we continue to perform the same steps as explained yesterday. First, we resolve the reexports for a given module, and then we proceed to do the same thing with the nested child modules.
This propagates down the resolution strategy to all modules we parsed from the crate. Then, at each level, we proceed to merge the original module with the list of resolution items.
These resolution items are one of a resolved pair of a path and a module, or an unresolved item path. Unlike yesterday, though, we perform one step before resolution and merging. This consists of use-normalization.
An initial pass to flatten all grouped imports within a single given import makes life much easier. After this, we can be sure that all reexports in a given module refer to a specific item (globs refer to specific modules.)
Normalization was a fairly simple algorithm to implement. It only needed me to recurse through the list of imports, and yield a singleton list for named and glob imports. Paths prepend to the result of recursing with the trailing tree.
Grouped imports are then ignored by appending the lists of (ungrouped) imports resulting from recursing the normalization function. Granted, this is implemented in terms of internal closures, but that is the key idea.
As mentioned yesterday, resolution consists of mapping each module individually through a function that resolves all reexports only in that module. Resolving a reexport goes through name lookup and recursion.
In the case of named paths in the use-tree, we perform lookups and yield resolved or unresolved items. These items are wrapped in a virtual module that ensures they get added when merging with the top-level “original” module.
In the case of a path, we first attempt the same lookup on the extracted head, and then recurse with the rest of the tree. We recurse only when it yields a module item, as other items can not produce paths.
The reason why I say so is because the types of items in which ctest is interested cannot produce paths themselves. Enumeration variants do not get tested against C bindings, so we can only have modules in paths.
This is one time where I wanted to use type constraints on the data constructors of my use-tree but hesitated. It would have been great to have the type for use-trees reflect the types of imports it accepts.
This way, I would be capable of avoiding partial functions (not defined over all of their domains; I.e. not exhaustively pattern-matched.) This being a demo for a future Rust program, I would like to keep the Idris niceties largely at bay.
The result of such resolution is a list of the afore mentioned resolution items. These then go through the merging pipeline with the top-level module that contains the reexports from which those resolution items were produced.
Upon finding an unresolved item, we simply recurse through the rest of the list. Upon finding a resolved item, we match the path of the item with some path in the reexports, so as to remove it from the module.
Then to actually merge the modules together, we just append to the container of the resulting module the items parsed from the resolution item. The simplicity here comes from having previously normalized the import paths.
Had we not flattened them, at this point we would have had to implement some more complex logic to remove only a fragment of a single grouped import statement, if the resolution item referred to an item yield from such import.
None.
All in all, I am fairly satisfied with today’s work. I have finished the draft implementation, and I am currently resolving some type checking issues. These are all mostly minor fixity precedence rules in the Idris program.
Once I am done with that, I will proceed to test it out with some manually-constructed modules and imports. If it works, then we can move on to thinking about the logic concerning the driver (as commented yesterday.)