|
|||||
|
|||||
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 export
ed.
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 import
s 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 import
s 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 require
d 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 |