Uranium is a semantic analysis framework. It is used to implement the semantic analysis stage of a compiler or interpreter: typing, building lexical and dynamic scopes, verifying and deriving other properties of interest at compile time.
Uranium works in a manner reminiscent of attribute grammars: it provides support for the computation of attributes on the nodes of an AST (Abstract Syntax Tree) - or really any set of objects, for that matter. These attributes may be interdependent, and a large part of Uranium's value-added is that it lets you specify these dependencies and how to compute attribute values from the value of dependencies, then automatically manages the propagation of dependencies.
Uranium is the perfect complement to Autumn, my parsing library.
You can see an example of Uranium in action in the demo language Sigh (SemanticAnalysis.java, SemanticAnalysisTests.java), and here is a code review of that code.
(Rough draft for now.)
-
An
Attribute
is a (node, name) pair. One of the objective of the framework is to assign values to attributes, which may be dependent on the value of other attributes. -
The
Reactor
is the central workhorse of the framework: it is responsible to compute the value of attributes from a set of user-defined specifications. -
You can supply basic attribute values directly by using
Reactor#set
. -
You can also specify rules (
Rule
) to compute attribute values dynamically by usingReactor#rule
. -
When you have registered all your basic values and rules, you can run the reactor with
Reactor#run
. This automatically propagates attribute values and trigger dynamic value computation through the registered rules. -
Rules are able to dynamically register new rules. This is sometimes necessary because the node to be used for an attribute can only be determined dynamically.
-
The initial set of values and rules are typically registered during a single-pass tree walk. I recommend using the abstract
Walker
class from my library norswap-utils. Its reflective implementations are particularly useful for prototyping. -
The other values/rules that can be registered through this pass are computed by
Reactor#run
and are those we have called "dynamic" above. -
The nature of semantic analysis is to check for potential errors (
SemanticError
). Uranium makes it easy to signal errors while not disrupting the computational flow. This lets the compiler derive as much information as possible. -
If an error precludes the computation of an attribute value, the error becomes the attribute value. However, that value is not propagate to rules depending on the attribute. Instead, the error is propgated: new errors are generated for the attributes computed by the dependent rules, signifying they can't be computed because of the original error.
-
You can inspect the result of a Reactor run by using the
AttributeTreeFormatter
class. It is meant for tree-like structures and required a norswap-utilsWalker
to walk the tree.
Be sure to check out the javadoc pages for Rule
and Reactor
which contains crucial information on
how to use the framework.
Your implementation of semantic analysis can be unit-tested using the UraniumTestFixture
class.