The Essence# Library Loader

The Essence# class library loader is responsible for loading all the entities defined in or more class libraries into an Essence# object space. The two fundamental issues that it was created to solve are a) bootstrapping, and b) the loading of class libraries and the entities they define in the correct sequence.

The bootstrapping issue arises due to the fact that an object space initially has no class libraries loaded at all, other than the classes and system primitive methods defined a priori by the run time system--or more specifically, by the object space. Such a priori initialization is one of the core responsibilities of an object space.

So one core job of the library loader is to do what the Essence# classes and methods defined a priori by the object space that contains them can't do yet: Load Essence# class libraries from persistent storage. The bootstrapping issue is larger than just the ability to compile source code, create classes, send dynamically-dispatched messages to objects or bind message sends to the fields, properties and methods of the instances of a CLR type--which is just about all that the set of classes and methods defined a priori by an object space can do, unless and until class libraries can be installed that augment and extend the capabilities of the object space.

The other core job of the library loader is to correctly serialize the operations required to load the namespaces, classes, traits, methods, variables and constants defined by one or more class libraries into an object space, without any formal specification provided by the class libraries being loaded of some known-to-work initialization sequence.

The sequencing issue is solved in three different ways:

  1. The Essence# Standard Library is always loaded first.
  2. Other class libraries are loaded in the order specified by the user or programmer.
  3. Provided certain constraints are satisfied by the code in a class library, there is a guaranteed-to-work sequence of operations for declaring, configuring, compiling and initializing the namespaces, classes, traits, methods, variables and constants defined by a class library--and so that, of course, is the sequence in which the library loader performs those operations.

The sequence the library loader uses to load create, configure, and initialize the entities defined by a class library is as follows:

  1. Every relevant folder and file in the class library is added to one or more lists of files/folders to be processed; the list(s) to which each such file or folder is added depends on the type of file or folder. It is during this procedure that the library loader determines for each folder in the class library whether it defines a namespace, class or trait. No Essence# code executes during this step.
  2. In hierarchical order (as defined by the namespace containment structure, not by the class inheritance structure,) every namespace, class, trait, variable and constant in the class library which doesn't already exist is declared, setting both its name and its parent namespace as specified by the class library. The term declare in this context means simply to create the entity with the specified name and add it as a child of the specified containing namespace (which might be a class or trait, since classes and traits are also namespaces.) No action is taken in the case of namespaces, classes or traits that already exist. No Essence# code executes during this step, although C# code that implements the primitive behavior of namespaces, classes and traits does get executed directly (without going through dynamic message dispatch or dynamic binding.
  3. In hierarchical order (as defined by the namespace containment structure, not by the class inheritance structure,) the configuration logic specified by each namespace.def, class.def, metaclass.def, trait.def and classTrait.def file in the class library is compiled and executed. So this is the first time that any Essence# code in the class library runs. And it's this code that establishes which namespaces import from other namespaces, what the inheritance structure of the classes will be, what instance architecture classes will have, and what traits will be used (if any) by the classes and traits defined by the class library. Note that all the methods needed to set the superclass of a class, define the instance architecture of a class, or define what namespaces will be imported are system primitives that have been defined a priori by the object space.
  4. In hierarchical order (as defined by the namespace containment structure, not by the class inheritance structure,) first the instance methods and then the class methods for all the classes and traits defined by the class library are compiled and added to their respective traits and classes. This is accomplished by compiling and executing all the "methods.instance" and "methods.class" files. Note that this only happens after the instance architecture and inheritance structure of the classes have been established. Note also that the only method usually needed to add a method to a class is the system primitive Behavior>>protocol:method:. However, there are several system primitive methods defined a priori by the object space which can be used to add methods to a class--and those can be used instead if they are needed for special cases.
  5. Each class and metaclass defined by the class library is activated. Until a class is activated, there are several constraints on the configuration of the class that it will not attempt to enforce (such as prohibited superclass-subclass relationships,) and several derived attributes/properties that it will not be computed. For example, if a class is supposed to represent a CLR type, it will not attempt to bind to that type (e.g, load it from its specified assembly) until it is activated.
  6. In hierarchical order (as defined by the namespace containment structure, not by the class inheritance structure,) the initializer for each namespace, class, trait, variable and constant is evaluated (by compiling and executing all the initializer files, all the ".variable" files and all the ".constant" files defined in the class library.)

Note that it does not matter that methods are added to traits only after the trait usages have been specified: Whenever a method is added to or removed from a trait that's being used by a class or by some other trait, the using class or trait marks its trait usage as invalid. When a trait usage has been marked as invalid, the correct set of methods to be imported will be recomputed the first time the user of the trait expression seeks to look up or enumerate methods from the trait(s) it's using.


The essence of OOP: It's all messages, all the time.


Last edited Aug 13, 2014 at 9:07 PM by Strategesis, version 26