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:
-
core.lang.PackageThis entity inherits from the
NameSetentity mentioned above and implements the concept of packages. In Storm, each package corresponds to a directory in the file system. The root of the name tree is a package that corresponds to theroot/directory in the file system. ThePackageclass is then responsible for creatingPackageentities for all subdirectories, and loading source code as appropriate. How source files are loaded is described here. -
core.lang.FunctionThis entity inherits from
Namedand represents a callable function. The parameters of theNamedare expected to correspond to the formal parameters of the function. This means that it is possible to store multiple functions with the same name, as long as they have different parameter lists. This ability is thus used to implement function overloading. -
core.lang.NamedThreadThis entity inherits from
Namedand represents a named thread, as understood by the type system. The entity essentially encapsulates a single OS Thread (core.Thread). -
core.lang.VariableThis entity encapsulates the concept of a variable. By itself it is not very useful, but Storm provides two subclasses for interoperability:
GlobalVar, andMemberVar. The former represents a global variables, and the latter represents a member variable of a type. -
core.lang.TypeThis entity inherits from the
NameSetentity mentioned above and implements types in Storm. As such, the type system is exposed to languages through this class. Each instance of the entity corresponds to a type in the type system (properties in the class determine what kind of type it is).Since
Typeinherits fromNameSet, it may contain other entities. While any named entity can technically be stored inside aType, functions and variables are of special interest to theTypeentity. Entities of the typeMemberVarare used to represent member variables, and theTypeentity uses them to compute the in-memory representation of instances of the class. Entities of the typeFunctionconstitute member functions. TheTypeentity keeps track of them to determine which functions require virtual dispatch. Note that non-static member functions are required to have athispointer as an explicit first parameter.
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.
