Named Entities
This page provides more detail on the central named entities that are provided by Storm, and how
they are used. As with most entities in the name tree, these entities can be extended by other
languages to provide additional functionality. Other languages may, however, not be able to
understand the additional information. As a concrete example, Basic Storm is not able to understand
the syntax from the Syntax Language, since the Syntax Language adds such information in a subclass
to the Type
entity that Basic Storm is not aware of.
The name tree is built from named entities. The name tree is a linked structure with pointers both from the root to its leaves, but also from the leaves towards the root. The former does not always exactly match the latter. For example, it is typically desirable to be able to perform type lookups inside blocks in functions. As such, the blocks have a pointer to its parent in the tree to allow traversing it towards the root to inspect the static context. However, it is typically not interesting for individual blocks inside a function to be reachable from the root (they are anonymous). As such, languages typically only adds a pointer to entire functions to the name tree, but functions themselves do not contain pointers to all the contained blocks.
NameLookup
A core.lang.NameLookup
represents something that can act as a starting point for name
resolution. The NameLookup
is itself not a named entity as it has no name. It is, however, one of
the base classes for named entities in the system.
To accommodate name lookups, the NameLookup
contains the following two members:
-
core.Maybe<core.lang.Named> find(core.lang.SimplePart part, core.lang.Scope source)
Find a named entity that corresponds to the specified
SimplePart
in this object. Returnsnull
if it is not found. The parametersource
indicates in what context the name lookup is performed, and it is used to determine the visibility of entities. -
core.Maybe<core.lang.NameLookup> parentLookup
Parent name lookup. This should be set by the parent. If it is
null
, the defaultparent
implementation asserts. Therefore, root objects need to overrideparent
in order to returnnull
.
Users of NameLookup
are encouraged to use the parent()
function instead of the parentLookup
variable directly. The default behavior of the parent()
function is to assert whenever
parentLookup
is not set, as this typically indicates that a named entity was not properly added to
the name tree. Implementations may, however, override parent
to return null
to indicate that the
root of the name tree has been reached (e.g. for the root package).
As mentioned above it is, however, possible to set parentLookup
manually to create entities that
are not reachable from the root of the name tree (and thus not reachable by most parts of the
system), but that may participate in name resolution. Typical examples of this is blocks inside of
functions in a programming language. These are anonymous, so there is no value in making them
accessible from the name tree. They do, however, contain variables, so it is useful to utilize the
name resolution system already in Storm to lookup local and non-local variables.
In addition, the NameLookup
class contains a number of convenience overloads for find
.
LookupPos
The type core.lang.LookupPos
is an intermediate class between NameLookup
and Named
.
It adds a source position to indicate the current location. This is mainly used to implement
file-private visibility. To determine if an entity is accessed from the same file, the visibilty
check examines if a LookupPos
is in the chain of parent
pointers in order to determine the
current source file.
Named
The core.lang.Named
is the base for all named entities in the system. It inherits from
LookupPos
, and thereby indirectly from NameLookup
. This means that it inherits a pointer to a
parent entity in the name tree.
Apart from the parent entity, the Named
type also contains a name. The name consists of two parts,
a string and a list of zero or more parameters. It is stored as two members as follows:
-
core.Str name
A string that represents the name of the entity.
-
core.Array<core.lang.Value> params
An array of
core.lang.Value
that represents parameters to this entity. EachValue
is essentially a reference to a type (but with some addtitional metadata, see below). Parameters are used to disambiguate different versions of the entity, for example overloaded functions and arrays that contain different types.
The named entity also contains the member flags
that indicate if the named entity should be
treated specially during name lookup. Currently, the only flag that is available is
NamedFlags:namedMatchNoInheritance
, which specifies that inheritance should not be considered when
matching parameters. This is typically desirable for templated types.
Apart from the name and flags, a named entity may also have a visibility. The visibility is
represented as an instance of a subclass of core.lang.Visibility
that is stored in the
visibility
member. If the visibility
member is null
, then the entity is considered to be
visible from anywhere in the system. The utility function visibleFrom
checks visibility using the
visibility
member.
Finally, the named entity may have an attached documentation entity in the documentation
member.
The function findDoc
can be used to retrieve the documentation conveniently.
The following convenience functions are also available:
-
core.lang.SimpleName path()
Compute a path to this entity. Utilizes the parent pointers to traverse the name tree. Will assert if not properly added to the name tree.
-
core.Str identifier()
Get a human-readable identifier for this named object. May return an incomplete identifier if the entity is not properly added to the name tree.
-
core.Str shortIdentifier()
Get a short version of the identifier. Only includes the name at this level with parameters.
-
void compile()
Force compilation of this entity (and any sub-objects contained in here).
-
void discardSource()
Discard references to source code stored in here. This is used to discard information required to re-compile the entity from source to save memory. Packages and types do this automatically unless instructed otherwise.
Function
The core.lang.Function
entity is a named entity that represents a function. The
parameters of the function entity are assumed to directly correspond to the formal parameters
accepted by the function, including any implicit this
parameters. The function also has a set of
flags, marking it as pure, autocast, final, override, or static. The pure flag means
that the system may assume that the function does not inspect nor modify any state outside of its
parameters. Calls to pure functions may thereby be optimized away if desired.
Note that functions that correspond to constructors (named __init
) are typically not possible to
call directly.
A function contains one instance of the core.lang.Code
class that contains the actualy
code that should be executed by the function. Apart from that, the function contains two
RefSource
s that other parts of the system may refer to. One RefSource
always refers directly to
the Code
in the function, while the other may be modified to instead refer to any logic required
for dynamic dispatch. Having two separate RefSources
means that call-sites can specify if the
dispatch logic should be called or not.
A function contains the following members:
-
core.lang.Value result
Return value of the function.
-
core.asm.Ref ref()
Get a reference to the code for this function.
-
core.asm.Ref directRef()
Get a reference directly to the underlying function, bypassing any vtable lookups or similar. Mainly used for implementing super-calls.
-
core.Bool isMember()
Is this a member function? A function is considered a member function if it is located inside a function, the first parameter of the function matches that type, and it is not static.
-
core.lang.RunOn runOn()
Which thread shall we run on?
-
core.Bool pure()
Is this function 'pure'? Ie. are we sure that this function does not produce any side effects? For constructors and destructors, this means that we can ignore calling the function when the compiler deems it unneccessary. For other functions, it means that the compiler may choose to perform constant folding or common subexpression elimination through this function safely.
-
void setCode(core.lang.Code code)
Set the code. Note: this safely updates all existing references to the code.
-
core.Maybe<core.lang.Code> getCode()
Get the code in use.
-
core.Maybe<core.lang.Code> getLookup()
Get the lookup currently in use.
-
void setLookup(core.Maybe<core.lang.Code> lookup)
Set lookup code. A default one is provided, use null to restore it.
-
void compile()
Forcefully compile this function.
-
core.FnBase pointer(core.lang.Function target)
Create a function pointer to a function.
-
core.FnBase pointer(core.lang.Function target, core.Object thisPtr)
Create a function pointer to a function. Binds
thisPtr
to the first parameter. -
core.FnBase pointer(core.lang.Function target, core.TObject thisPtr)
Create a function pointer to a function. Binds
thisPtr
to the first parameter.
The Function
class also contain a number of functions to automatically generate code for calling
the function. These are a number of variants, each with a number of overloads:
-
localCall
Generate code to call the function from a context where it is known the caller executes on the same thread as the callee.
-
threadCall
Generate code to call the function from a context where it is known that a message needs to be sent to another thread.
-
autoCall
Calls either
localCall
orthreadCall
depending on the provided context. -
asyncLocalCall
Generate code for an async call (i.e. returning a
Future
) for cases where the caller executes on the same thread as the callee. -
asyncThreadCall
Generate code for an async call (i.e. returning a
Future
) for cases where a message needs to be send to another thread. -
asyncAutoCall
Calls either
asyncLocalCall
orasyncThreadCall
depending on the provided context.
Code
The type core.lang.Code
is the base class to a hierarchy of types for different
situations. The ones most relevant when generating code from Storm are the followin ones:
-
core.lang.DynamicCode
Generates code from a
core.asm.Listing
object and allows executing it. -
core.lang.LazyCode
Generates a stub that calls the supplied function to generate the actual body of the function the first time it is executed. This is how lazy loading is achieved in large parts of Storm.
-
core.lang.InlineCode
Dynamically generated code that may be inlined elsewhere. The
InlineCode
class takes a callback function that is called to generate code. The function callback receives anInlineParams
object that contains the state of code generation, and information about parameters. TheInlineCode
class also generates a stand-alone version of the code to support creating function pointers to it, for example.
Variable
The core.lang.Variable
is a generic representation of a variable. It only has one
additional member, namely: type
that stores the type of the variable.
GlobalVar
The core.lang.GlobalVar
entity inherits from core.lang.Variable
and
represents a global variable. Global variables are expected to only be accessible from a particular
named thread to ensure thread safety. The global variable is lazily initialized by providing a
function pointer that contains the initialization logic.
MemberVar
A core.lang.MemberVar
entity is a variable that is a member of a type. As such, it does
not provide any storage for the actual variable itself, but cooperates with the Type
entity to
allocate storage in the type.
NamedThread
The core.lang.NamedThread
entity represents a named thread in the system. The
NamedThread
entity is essentially a thin wrapper around a Thread
object, that also contains a
name and allows accessing it easily in generated code through a reference.
NameSet
The core.lang.NameSet
entity is a named entity that may contain zero or more other named
entities. It is thus the type responsible for the central nature of the name tree. As the name
implies, all entities stored inside of it need to have unique names.
The NameSet
provides the following members for inspecting and manipulating its content:
-
void add(core.lang.Named item)
Add a named object.
-
void remove(core.lang.Named item)
Remove a named object from here.
-
core.Str anonName()
Get an anonymous name for this NameSet.
-
core.Array<core.lang.Named> content()
Get all members.
-
core.Maybe<core.lang.Named> find(core.lang.SimplePart part, core.lang.Scope source)
Find a named entity in the name set. Overloads the
find
function inNameLookup
. -
core.Array<core.lang.Named> findName(core.Str name)
Get all overloads for a specific name.
-
core.lang.NameSet.Iter begin()
Get iterators to the begin and end of the contents.
-
core.lang.NameSet.Iter end()
Get iterators to the begin and end of the contents.
Apart from storing entities, the NameSet
provides two mechanisms for dynamically generating named
entities on demand. The first one is through a mechanism called templates. A template in Storm
is essentially just a callback with a name without parameters. When a template is inserted into a
NameSet
, then the NameSet
will execute the callbacks in all templates associated with the name
that is beeing looked up whenever it does not already contain a suitable named entity. The templates
are thus free to inspect the parameters and determine if they wish to generate a suitable named
entity on demand or not. If they do, the named entity is inserted into the name set and used for
future lookups.
The functions used to manipulate templates are as follows:
-
void add(core.lang.Template item)
Add a template.
-
void remove(core.lang.Template item)
Remove a template from here.
The second mechanism is lazy-loading. This mechanism relies on overloading protected members, and
therefore it requires subclassing the NameSet
entity. A name set is initially in the unloaded
state. This means that whenever it fails to find a named entity (after template lookup), it will
attempt to load its content. This occurs in two steps. In the first step, the NameSet
calls the
function loadName
to attempt to load only the requested name. If it is successful, the name set
concludes its search and remains in the unleaded state. If loading of a single entity fails (e.g.
because it is not supported), then the loadAll
function is called instead. This instructs the
derived class to load all entities into the NameSet
. After this, the NameSet
switches to the
loaded state, which means that it will no longer attempt lazy loading.
In most cases, lazy loading is transparent to the user of the interface, as the NameSet
will
handle loading transparently. The exception is, however, iterating through the NameSet
. Doing this
will not cause automatic loading, as that would make it impossible to traverse the currently
loaded parts of the name tree. Instead, the forceLoad
member is available to force the NameSet
to load all its content early.
The following members are used for lazy loading:
-
core.Bool loadName(core.lang.SimplePart part)
Attempt to load a single missing name. Assumed to call
add()
on one or more candidates forpart
, and then return true. It is acceptable to add no candidates and returntrue
, which is interpreted asno matches can be lazy-loaded
. If there may be candidates, but none can be loaded from here, returnfalse
. This causesloadAll
to be called instead. -
core.Bool loadAll()
Called when we need to load all content. Assumed to call
add()
on all entities. Returns false or throws an exception on failure. Failure will re-try loading at a later time. -
core.Bool allLoaded()
Check if this
NameSet
is loaded fully. -
core.Maybe<core.lang.Named> tryFind(core.lang.SimplePart part, core.lang.Scope source)
Find a name part in this
NameSet
, but do not perform lazy-loading.
The default implementation of the loadName
and loadAll
functions above simply return false
and
true
respectively. This means that lazy-loading will succeed, but not load anything if they are
not overridden. This means that it is possible to ignore lazy-loading unless it is needed.
Package
The core.lang.Package
extends core.lang.NameSet
and represents a package in
the system. Packages generally correspond to directories in the file system, but it is also possible
to create virtual packages that do not exist on disk. Virtual packages must therefore be populated
manually.
The package entity add two main things to the interface of NameSet
: automatic discarding of source
code and exports. By default, a package will automatically ask all loaded entities to discard
references to source code after they are loaded. This means that it is not possible to ask functions
to re-generate intermediate code once they are created, for example. This is usually desired, as it
is only necessary to generate code for each function exactly once. In some cases, however, it is
desirable to inspect and modify the generated code. In such cases, the automatic discarding needs to
be disabled by calling noDiscard
.
The other concept, exports, allows a package to specify that other packages should be included
alongside it. For example, if a language extension in package a
adds to the syntax in b
, then
a
will not work unless b
is also included. To avoid confusion, a
can express this dependency
by exporting b
. This means that including a
from a language implicitly also includes b
.
The additional interface consists of the following members:
-
core.Maybe<core.io.Url> url()
Get an
Url
that represents the location where this package loads code from. If the package is virtual, returnsnull
. -
void noDiscard()
Inhibit discard messages from propagating.
-
void export(core.lang.Package pkg)
Add an exported package. This is most useful when creating virtual packages. Non-virtual packages read this information automatically as necessary.
-
core.Array<core.lang.Package> exports()
Get all exports for this package.
-
core.Array<core.lang.Package> recursiveExports()
Get all exports from this package, taking recursive exports into account.
Type
The entity core.lang.Type
also extends core.lang.NameSet
. It represents a
type in the type system. As such, it extends the interface of NameSet
with functionality for
managing inheritance, object layout, for example. The MemberVar
entity (see above) is specially
recognized by the Type
entity. They collaborate to provide object layout.
What kind of type is represented is set on construction through the TypeFlags
parameter. It can
also be retrieved using the typeFlags
member. The Value
class can also be used to inspect types
conveniently.
As with many other parts of Storm, the Type
class generates its contents lazily. This means that
it is possible to modify the contents of a type until the first instance is created. At that point,
the object layout is finalized, and members may no longer be added or removed. This restriction
does, however, not apply to functions that do not rely on virtual dispatch, or other named entities.
The Type
entity provides the following additional member functions:
-
core.Bool abstract()
Is this type abstract? Ie. should we disallow instantiating the object? By default, a type is abstract if it contains any abstract members, but languages may alter this definition to better suit their needs.
-
void setSuper(core.Maybe<core.lang.Type> to)
Set the super-class for this type.
-
core.Maybe<core.lang.Type> super()
Get the super-type for this type.
-
core.Maybe<core.lang.Type> declaredSuper()
Get the declared super-type for this type. This ignores the implicit
Object
orTObject
bases. -
core.lang.TypeChain chain
Inspect the inheritance chain, and perform quick membership lookups.
-
core.Bool setThread(core.lang.NamedThread t)
Set the thread for this type. This will force the super-type to be TObject.
-
core.lang.RunOn runOn()
Get where we want to run.
-
core.Array<core.lang.MemberVar> variables()
Get a list of all variables in the type.
-
core.Maybe<core.lang.VTable> vtable()
Get the VTable for this type.
-
core.asm.Size size()
Get this class's size. (NOTE: This function is safe to call from any thread. Use Value::size() from Storm if you don't want to switch to the Compiler thread).
-
core.asm.Ref typeRef()
Get a reference to this object that can be used in code generation.
-
core.asm.TypeDesc typeDesc()
Get a compact description of this type, used to know how this type shall be passed to functions in the system.