-
Notifications
You must be signed in to change notification settings - Fork 20
lang module
An Ecstasy module is the unit of compilation, the unit of versioning, and the unit of deployment. As a result, a module is also a natural unit for project organization. As an example, here's a small portion of the layout of the core "ecstasy" module from the Ecstasy project on GitHub:
./lib_ecstasy/ <- project
|- src/
| |- main/
| |- resources/ <- static resources
| | |- ...
| |- x/ <- source code directory
| |- ecstasy.x <- module file
| |- ecstasy/
| |- annotations/ <- a package within the module
| | |- Abstract.x <- a class within the package
| | |- Override.x
| | |- Transient.x
| |- collections/
| | |- Array.x
| | |- Collection.x
| | |- List.x
| | |- Set.x
| |- Boolean.x
| |- Const.x
| |- Enum.x
| |- Exception.x
| |- Object.x
| |- Service.x
| |- ...
|- LICENSE
|- README.md
|- build.gradle.kts
All Ecstasy source code is organized in .x
files, which are text files using the ASCII/UTF-8 encoding. (ASCII files are legal UTF-8 files.) Ecstasy source files in the module are organized hierarchically in a directory structure, matching the package structure within the module; a package in a module is like a directory on a disk. In the example above, the "ecstasy" module contains packages such as "annotations" and "collections".
The compiled form of an Ecstasy module is an .xtc
file. An .xtc
file is analogous to a .NET assembly, a Java .JAR
file, or an executable file (or .dll
/.so
) built from C or C++. The file contains a compiled intermediate representation (IR) of the source code, which is portable to any supported machine/OS combination. It also contains all of the resources specified by the source code, including templates, HTML, images, data and configuration files, and even entire directory trees. For most applications, it is desirable that the entire application could be deployable as a single .xtc
file.
Like directories in a file system, modules are hierarchical. A module can contain packages and classes, and packages can in turn contain more packages and classes, and classes can contain more classes. Modules and packages are just two special forms of classes!
An entire module and everything that it contains can be organized within a single .x
file -- even if it contains thousands of packages and classes! At the other extreme, the module can be maximally spread out across files, with one file for each module, package, and class, organized in a directory hierarchy that corresponds to the module's own hierarchy. Or the source code organization can fall anywhere between those two extremes, with some packages and classes being split out into their own .x
files and directories, and others defined "inline" within the parent module, package, or class definition.
This allows a developer to use a single file for a tiny module, like the "Hello World!" example. It also allows very large modules to be organized in a manner that is conducive to team development and version control systems. And it allows for small classes to be defined within the class' enclosing namespace, without requiring each class to be split out into a tiny file of its own.
Regardless of how that choice is made, and regardless of the number of directories and source files that make up a module, from the language's point of view, a module is treated as single unit, and it is compiled as if it were a single file. In other words, it would be perfectly acceptable for a compiler pre-processor to take all of those files, and merge them together into one giant file (following the syntax rules of the language), and the compilation result would be identical -- other than the file-name and line-number information used for stack traces and debugging.
There are two ways to name the file that contains the module declaration code:
- The default naming rule for the module file is to use the module's name (either unqualified like "
ecstasy
", or qualified like "ecstasy.xtclang.org
"), plus the.x
source file extension. To split out packages or classes, each would be placed in their own file in a sub-directory whose name matches the module file name -- but without the.x
extension. The following example shows the moduleMyApp
, a nested classProfile
and packageutil
, and a classRunner
inside theutil
package:
./myapp/
|- src/
|- main/
|- x/
|- MyApp.x <- module "MyApp"
|- MyApp/
|- Profile.x <- class "MyApp.Profile"
|- util.x <- package "MyApp.util"
|- util/
|- Runner.x <- class "MyApp.util.Runner"
- If the entire module is stuffed into a single
.x
file, then the name of the file does not have to match the name of the module. In this case, it would not be possible to break out contents of the module into their own separate files in a sub-directory without first matching the module file name to the name of the module itself, as illustrated above in theMyApp
example.
In the MyApp
example above, note how similarly the nested package structure is to the module structure itself. There is one important difference, though: A package is often represented simply by the presence of a directory. In other words, a sub-directory in a source tree implies the presence of a package of that same name. If the util.x
package source file in the example above were deleted, then the compiler would infer the following code for the package:
package util {}
In other words, the presence -- and just the presence! -- of that sub-directory implies that there is a piece of code defining the package. That's about as simple as it gets.
Packages are name-spaces within a module, just like directories are name-spaces within a file system. Packages can in turn contain more packages and classes. Most of the time, those are organized hierarchically within the package's directory, but -- as is the case with modules -- the contained packages and classes can be textually contained within the package's source code itself. This flexibility allows small class and type definitions to be included directly into the source code of the package, and allows the larger classes within the package to each have their own .x
file.
The syntax for declaring a module or a package is the same syntax as declaring a class, except the keyword module
or package
is used instead of class
. The name of the module can be a qualified name, which means that it is suffixed with a domain name indicating the organization responsible for the module:
module commons.apache.org {
// all the code for the entire module
// could be placed right here
}
The use of the domain name as part of the qualified module name is quite purposeful; here are some intended uses for leveraging the domain name:
- The basis for Internet module repositories, and downloading modules by their names; and
- The basis for private key module signing with public key validation.
It is helpful to think of a module in the way that you would think of a storage drive: The module corresponds to the root of the storage, packages are like directories within that storage, and classes are like files in those directories. The analogy is flawed, but it does provide a basis for visualizing the structure of a module. For example, the following module M
contains the package P1
that contains the package P2
that contains the class C
:
module M {
package P1 {
package P2 {
class C {
}
}
}
}
This guide is intended to help developers quickly get acquainted with the language, and not as an exhaustive examination of all possible syntactic variations, so we won't go into arcane module
and package
examples with generic type parameters, constructor parameters, and so on. For future reference, just remember that all of the bells and whistles that can be applied to classes, can also be applied to modules and packages, with only a few exceptions.
Modules and packages can be annotated, extend an existing class, implement interfaces, and incorporate mixins. For example, the Database
mixin from the oodb.xtclang.org
module is used to indicate that a module is a database schema. However, to use that mixin, the using module mounts the other module into its namespace as a package, so that the name Database
can be resolved:
@Database
module AddressBookDB {
// mount the library "oodb.xtclang.org" into this module as "oodb"
package oodb import oodb.xtclang.org;
// import the mixin "Database" from the library "oodb.xtclang.org",
// so that we can refer to it with its simple name "Database"
import oodb.Database;
// ...
}
In the above example, the package statement creates a package oodb
within the AddressBookDB
module, but that package is just a reference (or a redirect) to a different module, in the same way that a file name is used as a mount point in UNIX or Linux. In other words, the AddressBookDB
module contains an empty oodb
package that itself acts as a reference to the oodb.xtclang.org
module. The only two places in the Ecstasy language that allow the use of the qualified module name are in the module
declaration statement itself, and in the package
declaration statement when it used to specify a module to import.
If you remember back to that initial Ecstasy "list of axioms", one was "The Ecstasy language provides a core "ecstasy" module, which is automatically present in every type system." The way that works is that every module implicitly contains a package named ecstasy
, as if the following line of code were added to each module as part of its compilation:
package ecstasy import ecstasy.xtclang.org;
During compilation, names are resolved by asking the code's compiler context if it knows what the name means, and if it doesn't, then asking the next outer containing context, and so on, all the way out to the context of the module itself. So everywhere within a module, the name "ecstasy" will refer to the ecstasy.xtclang.org
module -- unless of course someone re-uses the name ecstasy
for something else, which would obscure (shadow) the package name.
This approach to name resolution means that you can place constants, functions, type definitions, and even imports onto a package or module, and those names will be available everywhere within that context. It's a powerful way to achieve many of the useful benefits of globally defined names, without the downsides of actually having any "globals".
-
Modules and packages are
const
classes. Objects instantiated fromconst
classes are immutable, and Ecstasy automatically provides an implementation of the Comparable, Orderable, Hashable, and Stringable interfaces, as specified by the Const interface. Examples of otherconst
classes include String and Int classes, which should give you some idea of what aconst
is. (We'll cover this topic in detail in a subsequent chapter.) -
Modules and packages are singleton classes. This means that the runtime will automatically instantiate and hold on to one instance of the class, and no other instance of the class can be created. Furthermore, it means that the singleton instance is always easily available -- by its name!
-
In the case of a module, because all the code in the module is nested inside of the module, the name of the module is visible from any of that code. Alternatively, a reference to the current module is always available as
this:module
. Take the example ofMyApp
, introduced previously in this chapter; anywhere inside of that application, one can obtain a reference to the module in either of the following ways:
MyApp m1 = MyApp;
MyApp m2 = this:module;
- Ecstasy automatically provides an implementation of the Module interface for each
module
, and an implementation of the Package interface for eachpackage
. TheModule
interface extends thePackage
interface, so any question that a package can answer, a module can also answer (but the converse is not true).
Prev: Introduction to the Ecstasy Language | Next: Your first "Hello World!" |
---|