![]() |
|
||||
![]() ![]() ![]() ![]() ![]() ![]() ![]() |
|||||
|
|
|||||
| Related Syntax | |||||
Modules |
|||||
A module is a self-contained unit of OmniMark code. It can have its own include files and its own macros. It has control over what names its "clients" (importers) see, and the client has control over what names it uses for the things it gets from the module.
It is useful to think of a module as being like an "include" file, but with control over the names defined and used within it.
By convention, OmniMark modules are placed in files whose names end with the ".xmd" suffix, in the same way that OmniMark programs use ".xom" and OmniMark include files use ".xin".
Here's a simple module with a function that returns a different (pseudo-random) number each time it's called:
module
global integer count initial {1}
export integer function next as
set count to (count * "ABCDEF97" base 16) shift -1
return count
The module starts with the keyword module. module can be preceded only by comments and white-space.
After that, things are more or less as they are for any other OmniMark code. You can declare globals, constants, functions and opaque types. You can have rules, including process-start and process-end rules.
(In this and many of the following examples, we're only exporting and importing a single thing from the module. This is just to keep the examples simple. In practice there will be lots of things exported, and lots of things not exported.)
By default, anyone using (importing) a module doesn't see the names within it -- they're hidden. The module can selectively export names from the module, either by replacing the keyword define or declare that normally starts a declaration with the keyword export, as in:
export function output-header as ; ... export integer function next as ; ... export catch end-of-group
or by preceding the keyword global or constant that normally starts a declaration with the keyword export, as in:
export global byte-count initial {0}
export constant readAccess initial {1}
You can export:
There are some kinds of properties that will always remain "private" to modules, and which are not intended to ever be exported.
Once you've defined a module, you can use it in OmniMark programs and other modules by "importing" it:
import "mymodule.xmd" unprefixed
The name after the keyword import is a file name, just as for an include file. The only other thing you absolutely need to do is to tell OmniMark:
unprefixed is the simplest way of dealing with this requirement. unprefixed says "import everything, and use each name as-is".
As an example, if "mymodule.xmd" is the "next number" module described earlier, then you can use the name of the exported thing -- i.e. "next" -- as-is:
import "mymodule.xmd"
unprefixed
process
repeat to 10
output "10fkd" % next || "%n"
again
If you want to keep the names of things imported from a module separate from your own, the simplest thing is to specify that you want them "prefixed". In this case, all the things imported will have "my." prefixed to their names for the purposes of the importer. So, for example, using the same module as above, the use example would be:
import "mymodule.xmd"
prefixed by my.
process
repeat to 10
output "10fkd" % my.next || "%n"
again
There are no limitations on what you use as a prefix, so long as the result is a valid name. Remember that OmniMark allows "-", "." and "_" in a name, and these are good things to put at the end of prefixes. On the other hand, a prefixing name is often good enough.
Each module has its own set of groups. Independently of what they are named, no group can have rules within it from different modules. This module specificness of groups extends to the #implied group: each module has its own #implied group.
Each module has its own current group. The setting of a group within one module does not affect the current group of any other module. The default current group for a module is that module's #implied group.
The effect of the above is that rule selection always selects from within a single module.
Sometimes you don't want everything a module exports. Sometimes you want to avoid name clashes, but prefixing everything is just too ugly. Sometimes you've already got a name for something and you want to use it. This is where selective importing comes in.
Following unprefixed or prefixed by, you can put one or more use directives:
import "mymodule.xmd"
unprefixed
use next as random
process
repeat to 10
output "10fkd" % random || "%n"
again
If you want to full control what's imported from a module -- for example, when you just want one or two things from a general utility-supplying module -- you can specify only in place of unprefixed or prefixed by. only means "only import what I've listed below by use". For example:
import "mymodule.xmd"
only
use next ; you can rename or not, depending on what you need
process
repeat to 10
output "10fkd" % next || "%n"
again
There's one rule that you need to be familiar with when you're using use. When you're importing a module, you only get one name for each thing imported. So if you say use for something imported, it won't be assigned another name, even if it says prefixed by at the top of the import.
use is useful for dealing with conflicts between names imported from different modules.
Imported modules can be parameterized. You can supply a module you're importing with functions, globals, constants, catches and opaque types.
Module parameterization is the inverse of exporting. Exporting ships things from within modules to the outside -- from the modules to their importers. Module parameterization ships things from outside of modules into them -- from the importers into the modules being imported. Exporting is what you mostly want to do, but parameterization is occasionally what you have to do. The most common example is supplying a "call back" function to a module, such as supplying a comparison function to a sorting module.
There are two parts to supplying module parameterization. Within the module you have to declare what you require from whoever's importing you. You do this by providing declarations of the things to be supplied, but with the keyword require in place of define or declare or prefixing global or constant. For functions, you leave out the as or elsewhere part. For globals and constants you only specify the type and name. And for opaques you leave out the creator. For example:
require opaque sortable-type require switch function compare (value sortable-type a, value sortable-type b) require global integer total-count require constant integer maximum-count require catch maximum-count-exceeded by value stream who
The other half of parameterizing modules is supplying the required things from whoever is importing the module. Supplying consists merely of following the keyword supply with name of the thing to be supplied -- the thing has to be declared prior to the supply, so nothing more need be said about it. On the other hand, you may have to tell the import which of its requirements this supply is satisfying -- which you do following as. For example:
import "sort.xmd" prefixed by person-
supply person-record as sortable-type
supply compare-person as compare
The as and the following name can be left out when the two names are the same, as in:
import "countfiles.xmd" unprefixed
supply total-count
supply maximum-count
supply maximum-count-exceeded
supply and use parts of an import can be mixed together and put in any order.
When there are two or more imports of a module, either in the same program or module or in different modules, one can get either:
Both techniques have their uses:
float type, you'll probably want all users of the type to use the same type -- so that its values can be interchanged.
The sharing/non-sharing distinction is determined by a module definition, not by its importers. You specify that a module is shared by following the keyword module at its head by shared as and a name, as in:
module shared as float-module ; The rest of the floating point math. module. ; Shared because it has no local state, and ; because the "float" type needs to be common to all uses.
When a module is shared, the following apply:
import of the shared module is used by the compiler. All other imports of modules with the same "\shared as)" name use the same compiled module, even if their source code is different from the first one.
opaque type is exported by a shared module, all its importers get the same type. opaque types exported from non-shared modules produce a distinct type for each import.
process-start rules of a shared module are run as though they were declared where it is first imported. The process-end rules of a shared module are run as though they were declared where it is last imported (even though those process-end rules are taken from where the module is first imported). This ordering of things helps ensure that modules are initialized early enough, but not shut down too early.
shared module isn't allowed require anything. Because an instance of a shared module can be imported from more than one location, if it required anything, it would have to take what's supplied to it from one of its importers, and ignore what's supplied by other importations, and that would result in ambiguity and unexpected behavior.
Names, user-defined names and OmniMark's built-in names both, fall into four categories vis-a-vis modules:
#main-input and #main-output are examples.
#current-input is an example.
#item is an example.
Values specified on the command line are specific to the main program, or affect all modules, on a case-by-case basis, as follows:
global names defined in the main program can be set from the command line.
#library, #libpath and #libvalue shelves. The command-line settings for these shelves, on the other hand, are individually applied to each set of shelves, so that they are available in the main program and in each module.
include-guard applies individually to each module. Each module has its own include guards.
include-guard protects against duplication of include text -- so you don't get the same definitions twice. But just because one module has a copy of the text doesn't mean that all modules have access to that text. The idea is that each module gets one copy.
When modules are used:
elsewhere declarations can be used for functions that are later defined by being imported from a module or being something your module has a require for.
elsewhere definitions whose "proper" definitions are imported from a module or are the result of a require are not allowed specify initial values for their optional arguments.
A variety of OmniMark program properties have been made local to each module. In addition to those mentioned above:
set external function of reader to "utf8Reader" in function-library "utf8filter.xmd"
insertion-break and replacement-break rules bound to a stream -- that supports line breaking -- are those from the module in which a stream was opened.
|
Related Syntax export, opaque require |