swirl
Guide to OmniMark 7   OmniMark home
docs home 
IndexConceptsTasksSyntaxLibrariesLegacy LibrariesErrors
 
    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:

What's Not Exportable

There are some kinds of properties that will always remain "private" to modules, and which are not intended to ever be exported.

Using Modules

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.

Groups and Modules

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.

Selective Importing and Selective Naming

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.

Parameterizing 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.

Shared Modules

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:

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:

Names and Modules

Names, user-defined names and OmniMark's built-in names both, fall into four categories vis-a-vis modules:

Main Program Parameterization

Values specified on the command line are specific to the main program, or affect all modules, on a case-by-case basis, as follows:

Include Guard

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.

Predefining Functions

When modules are used:

Module-Specific Properties

A variety of OmniMark program properties have been made local to each module. In addition to those mentioned above:

      Related Syntax
   export, opaque
   require
 
 

Top [ INDEX ] [ CONCEPTS ] [ TASKS ] [ SYNTAX ] [ LIBRARIES ] [ LEGACYLIBRARIES ] [ ERRORS ]

OmniMark 7.1.2 Documentation Generated: June 28, 2005 at 5:44:39 pm
If you have any comments about this section of the documentation, send email to [email protected]

Copyright © Stilo Corporation, 1988-2005.