The Name Tree

The name tree is perhaps the most central component in Storm. As the name implies, the name tree is a tree-like data structure that associates names with various elements in the system. The name tree thus forms a global namespace that is shared between all languages in the system. The name tree is also where Storm exposes its functionality to the languages in the system.

Basic Structure

The name tree is created from two central classes: Named and NameSet. The former, Named, is a generic representation of something that has a name (a named entity). The latter, NameSet, inherits from Named, and may additionaly contain zero or more named entities. As such, NameSet allows creating nested namespaces. This is used to implement packages and types among other things.

Each instance of Named stores its own name. The name consists of a string, and zero or more parameters. Each parameter is a reference to a type in the type system. Two entities are considered to have the same name only if both the string and all parameters are equal. This allows overloading names with different parameters. This is used to allow function overloading, and to implement parameterized types (e.g. Array).

Finally, a note on terminology. The documentation uses named entities to refer to objects that are reachable from the name tree. This term is simply used to highlight the fact that these objects have a name, and that they are easily reachable from multiple languages. However, these named entities are still regular instances of an actor type that inherits from Named.

Names

Storm provides a representation of names to elements in the name tree. Similarly to how paths refer to elements in the file system, a name in Storm consists of zero or more parts. Each part consists of a string and zero or more type parameters, just like the name stored in the Named class. In the standard textual representation, each part is denoted as <name>(<parameter>, <parameter>, ...). If no parameters are specified, the parentheses are omitted. Different parts are delimited by periods (.). Below are some examples of names in the system:

core.Int                       // Name of the 32-bit integer type in Storm
core.Int.+(core.Int, core.Int) // Name of the addition operator in the Int type
core.Array(core.Int)           // Name of an array of integers
core.lang.rootPkg              // Name of the function to acquire the root package of the name tree

Note: The above representation is used by Storm itself. The notation used in Basic Storm is slightly different for technical reasons. Namely, : is used instead of ., and <> are used instead of ().

Storm provides two representations of names in non-textual forms. The first one is called a SimpleName. A SimpleName is an array of SimpleParts, each consisting of a string and zero or more type parameters. In the simple representation, all type parameters are pointers to type entities in the name tree.

The other representation, Name is a sequence of NamePart objects. Each NamePart may be a SimplePart as for SimpleNames, but it may also be a RecPart. The difference between a SimplePart and a RecPart is that the parameters of a RecPart (for recursive part) are not pointers to entities in the name tree, but instead unresolved names. The difference is thus that all parameters in a SimpleName are resolved, while a Name may need recursive resolution of its parameters.

Named entities

It is possible to store arbitrary data in the name tree by creating a class that inherits from Name or NameTree as appropriate. Since the name tree is shared between different languages, this is the primary mechanism which languages use to communicate with each other. To facilitate interaction, Storm specifies a number of pre-defined entity types for common concepts, such as functions and types. Languages that produce and consume entities that follow the Storm-specified interfaces are thus able to interact freely. Languages are, however, not limited to using the standard interfaces. They are free to create their own representations where the standard representation is not enough. This will, however, mean that other languages are likely to interact with those features of the language.

Some of these Storm-provided named entities that are of particular importance are as follows:

Name Lookup

Storm provides a customizable way of traversing the name tree to resolve names into named entities. This process is encapsulated by the type Scope. A scope consists of a pointer to a NameLookup and a ScopeLookup. The NameLookup determines the "current" location in the name tree (similarly to the working directory in the terminal). The Named class inherit from NameLookup, so all Named entities can be used for this purpose. The separation between Named and NameLookup allows the creation of anonymous scopes (e.g. blocks in imperative languages) that can be used with the standard name lookup. The ScopeLookup specifies a strategy for resolving names. The strategy dictates how the NameLookup should be used when attempting to resolve a name. For example, the ScopeLookup might decide to attempt to resolve names in all parent NameSets, or it might decide to only consider Types, the first package, and then a set of explicitly included packages. This mechanism means that a language is able to customize the logic behind the name lookup to a high degree, and to even share its name resolution logic when interacting with other languages. Since the "current location" is separated from the lookup logic, it is also easy to modify the "current location" while retaining the name resolution logic.

Visibility

Each Named object may optionally be associated with a visibility. The visibility determines which parts of the system is allowed to access a particular Named. This mechanism is used to implement access controls, such as public and private, but allows more flexibility if desired.

The visibility of a Named object is represented by instances of subclasses to the Visibility class. This class is not much more than a predicate function, visible(Named check, NameLookup source) that determines if the entity check is visible from the source. This function is called from the function Named.visibleFrom during name lookup to determine visibility. There are a couple of default implementations for common keywords provided by Storm. For example, the classes Public, TypePrivate, TypeProtected, PackagePrivate, etc.

The visibility is assessed during type lookup. Thus, it is necessary to provide a Scope that describes the caller's scope when calling NameLookup.find(). It is also possible to override the visibility system by providing an empty scope (i.e. Scope()) to the find function. If a Named object is not associated with a Visibility object, Storm considers it to be visible from everywhere.