








                          FST (Fitted) Modula-2 Version 4.0




            (c) Copyright 1987,1988,1992,1995,1996 Fitted Software Tools.
                                All rights reserved.







                               DISCLAIMER OF WARRANTY



         THIS  SOFTWARE  AND   MANUAL  ARE  PROVIDED  "AS  IS"  AND   WITH-OUT
         WARRANTIES AS TO PERFORMANCE OR MERCHANTABILITY.



         THIS SOFTWARE IS  PROVIDED WITHOUT ANY EXPRESS OR IMPLIED  WARRANTIES
         WHATSOEVER.  BECAUSE OF  THE  DIVERSITY OF  CONDITIONS  AND  HARDWARE
         UNDER WHICH THIS SOFTWARE MAY BE  USED, NO WARRANTY OF FITNESS FOR  A
         PARTICULAR  PURPOSE IS  OFFERED. THE  USER IS  ADVISED TO  TEST  THIS
         SOFTWARE THOROUGHLY  BEFORE RELYING ON IT.  THE USER MUST ASSUME  THE
         ENTIRE RISK OF USING THIS SOFTWARE.






                                    To my parents





         I implemented FST  Modula-2 for my mother; I  could not have done  it
         without my father's support and encouragement.

         All that is good in me, I owe them.





                                                                Roger Carvalho



                                   Table of Contents


       1. ABOUT RELEASE 4.0..................................................1


       2. INTRODUCTION.......................................................2

       2.1 HARDWARE REQUIREMENTS.............................................2
       2.2 SOFTWARE REQUIREMENTS.............................................2
       2.3 FOR USERS UPGRADING TO RELEASE 3.5/4.0............................2

       3. SOFTWARE INSTALLATION..............................................3

       3.1 THEORY............................................................3
       3.2 RECOMMENDED SETUP FOR A HARD DISK SYSTEM..........................4

       4. THE M2.CFG FILE....................................................5


       5. A LITTLE TOUR THROUGH THE SYSTEM...................................6

       5.1 THE TOUR..........................................................6

       6. THE COMPILER.......................................................9

       6.1 THE INTEGRATED COMPILER: MC.......................................9
       6.2 THE FREESTANDING COMPILER: M2COMP................................11
       6.3 THE COMPILATION PROCESS..........................................11
        6.3.1 The input file ...............................................11
        6.3.2 The imported modules .........................................11
        6.3.3 The output file ..............................................12
       6.4 A WARNING........................................................12
       6.5 COMPILER DIRECTIVES..............................................12
       6.6 RUNTIME ERRORS...................................................13
        6.6.1 Trapping runtime errors in your program ......................13
       6.7 COMPILER SIZE LIMITS.............................................13
       6.8 THE LANGUAGE SUPPORTED...........................................14
        6.8.1 LONGINT and LONGCARD .........................................14
        6.8.2 LONGREAL .....................................................15
        6.8.3 Additional or augmented standard procedures ..................15
       6.9 OBJECTS EXPORTED BY THE PSEUDO MODULE SYSTEM.....................16
       6.10 THE GENERATED OBJECT CODE.......................................21
        6.10.1 Data type representation ....................................21
        6.10.2 The runtime memory map ......................................22
        6.10.3 Procedure calling conventions ...............................23
       6.11 MODULE PRIORITIES...............................................24
       6.12 MEMORY MODELS...................................................24

       7. USING OBJ FILES...................................................26

       7.1 GENLINK..........................................................26
       7.2 FOREIGN MODULES..................................................26
        7.2.1 External names ...............................................26



        7.2.2 Implementation ...............................................27
        7.2.3 In the real world... .........................................28
       7.3 `C' RUNTIME OPTION...............................................29

       8. THE TEXT EDITOR...................................................30


       9. THE LINKER........................................................32

       9.1 MODULE KEYS......................................................32

       10. OTHER UTILITIES..................................................34

       10.1 EDITOR CONFIGURATOR.............................................34
       10.2 MAP FILE GENERATOR..............................................34
       10.3 MAKE AND THE MAKEFILE GENERATOR.................................34
       10.4 M2O FILE DECODER................................................35

       11. THE LIBRARY MODULES..............................................36

       11.1 RELEASE 3 LIBRARIES.............................................36

       12. THE RUNTIME SUPPORT SYSTEM.......................................37


       13. MODULA-2 SYNTAX..................................................38


       14. OO EXTENSIONS....................................................40

       14.1 HIGHLIGHTS OF THE IMPLEMENTATION................................40
       14.2 CLASSES, ATTRIBUTES, METHODS, INHERITANCE AND POLYMORPHISM......41
       14.3 DEFINING CLASSES IN DEFINITION MODULES..........................44
       14.4 IMPLEMENTING CLASSES IN IMPLEMENTATION MODULES..................45
       14.5 SELF............................................................47
       14.6 SUPER...........................................................48
       14.7 OBJECT INSTANTIATION, CREATION AND DESTRUCTION..................48
       14.8 OBJECT COMPATIBILITY............................................49
        14.8.1 Assignment compatibility ....................................49
        14.8.2 Expression compatibility ....................................50
        14.8.3 Parameter compatibility .....................................50
       14.9 THE MEMBER FUNCTION.............................................51
       14.10 COMPATIBILITY BETWEEN OBJECTS AND OTHER TYPES..................51
       14.11 POINTERS TO CLASSES............................................51
       14.12 COMPLETE EXAMPLE...............................................52
       14.13 VIRTUAL METHODS................................................54
       14.14 DATA STRUCTURES WITH CLASSES...................................59
       14.15 THE TECHNICAL DETAILS..........................................62
       14.16 ON YOUR OWN....................................................63

       15. LICENSE TERMS....................................................64

       FST Modula-2                                                          1


       1. About release 4.0

       This is the final release of FST Modula-2.

       Release 4.0  this is  no more  than  a re-packaged  release 3.5.    The
       software was not changed to reflect the new release number.

       The  only  changes   in  this   package  are   in  the   documentation;
       specifically, the software license  changed, as Shareware  registration
       is no longer available.

       FST Modula-2                                                          2


       2. Introduction

       This system features a Modula-2 compiler with an integrated editor  and
       "make"  facility,  a  program  linker,  a  makefile  generator  and  an
       execution profiler.

       The compiler generates code for the Intel 8086 "huge" or "large" memory
       model: In the "huge" memory   model, each module  has its own data  and
       code segment, each of which can  be up to 64k  in size; In the  "large"
       memory model, all the  modules' static data is  combined into a  single
       data segment.  In  either model, pointers are  4 bytes long and,  after
       program loading, all the leftover memory  is available for the  "heap".
       More restrictive memory models are not supported.


       2.1 Hardware requirements

       This system will run  on IBM PC, PC/AT,  or compatible systems with  at
       least 512K of RAM, two double sided floppy disk drives and a monochrome
       display adapter, color graphics adapter or equivalent.

       A hard disk and 640K of RAM are, however, recommended.


       2.2 Software requirements

       This system requires DOS version 2.0 or later.

       No other software is required to use this system, but you will need  an
       assembler if you intend to modify one of the following runtime  support
       modules: M2Reals (floating point support), M2Longs (LONG arithmetic) or
       M2Procs (coroutine handling).


       2.3 For users upgrading to release 3.5/4.0

       Release 3 added language extensions designed to support object oriented
       programming.  Release 3.5/4.0 adds several enhancements to this set  of
       language extensions.  Please refer to the chapter on OO Extensions  for
       more information.

       It  is  now  legal  to   return  arbitrary  structures  from   function
       procedures.

       Interfacing Modula-2 and C may be a little easier now when you use  the
       new compiler's `
                      `C runtime environment'
                                             ' option.  You also now have  the
       ability to implement `
                            `FOREIGN C'
                                       ' modules.

       The compiler and utilities now read the M2.CFG file on startup.

       FST Modula-2                                                          3


       3. Software installation

       Before  starting,  please  print  the  file  READ.ME,  which   provides
       information about the distribution contents.


       3.1 Theory

       The library  module  TermIO  (through  which  Terminal  does  its  I/O)
       requires that the ANSI.SYS driver be installed.

       The executable  files (MC.EXE,  M2LINK.EXE, ...)  should, for  ease  of
       operation, be  placed  in  a drive/directory  named  in  the  DOS  PATH
       variable.

       The editor configuration file M2ED.CFG  must be accessible through  the
       DOS PATH.

       Both the  compiler and  the linker  will use  the environment  variable
       M2LIB to  locate required  library modules  (the format  for the  M2LIB
       entry matches that of the DOS PATH).

       The environment variable M2MODEL may be  set to 'HUGE' or 'LARGE'.   If
       M2MODEL is not set, the compiler, by default, assumes that you want  to
       compile for the HUGE model.

       The compiler and utilities in this package do not keep many files  open
       simultaneously.  If you set the FILES parameter in CONFIG.SYS to 10 you
       should not encounter any problems.

       Example CONFIG.SYS:

            DEVICE=ANSI.SYS
            FILES=10
            BUFFERS=20

       The compiler supports 2 different memory  models.  If you are going  to
       be using both memory models, keep each set of library object files in a
       different directory, and change the M2LIB  path according to the  model
       in use.  We use the following BAT files to take care of this:

            LARGE.BAT:
                 set M2MODEL=LARGE
                 set M2LIB=C:\M2\LIB;C:\M2\LIB\L
            HUGE.BAT:
                 set M2MODEL=HUGE
                 set M2LIB=C:\M2\LIB;C:\M2\LIB\H

       where \M2\LIB is the directory where all the DEF and BIN files  reside,
       \M2\LIB\L contains the LARGE memory model M2O files, ...

       In the following discussion we will,  for simplicity sake, assume  that
       you will be using only one of the memory models.

       FST Modula-2                                                          4

       3.2 Recommended setup for a hard disk system

       Place  the  executable  files  (*.EXE)  and  M2ED.CFG  in  a  directory
       currently in the DOS search path, or in a new directory (ex: \M2) to be
       added to the PATH list.  Example:

            SET PATH=C:\BIN;C:\M2\BIN

       Make a directory for the library  files (ex: \M2\LIB) and copy all  the
       .DEF, .M2O and  .BIN files to  it.  To  the AUTOEXEC.BAT  file add  the
       line:

            SET M2LIB=C:\M2\LIB

       You will probably  want to  create a  directory for  your own  reusable
       library modules.  This directory can be added to the M2LIB  environment
       variable.  Example:

            SET M2LIB=C:\M2\LIB;C:\MYLIB

       You may keep your projects in their own, individual, directories.

       FST Modula-2                                                          5


       4. The M2.CFG file

       Instead of using environment variables to set compiler options, you may
       use the M2.CFG file.

       All this system's programs attempt to  locate the M2.CFG file on  start
       up.  If this file is found in one of the PATH directories, its contents
       are used to set the program's default options.

       Environment variables can  be used to  override options  in the  M2.CFG
       file.

       Default command line options for the various programs can also be coded
       in the M2.CFG file.

       Example M2.CFG:

            m2model large
            m2lib \rel3\comp\libc;\rel3\lib;\rel3\lib\l
            mc /p mc /s 2013 14000 /d-
            m2link /l /v

       In this example we specify  the use of the  LARGE memory model, we  set
       our M2LIB path, and  specify the default command  line arguments to  MC
       and M2LINK.

       FST Modula-2                                                          6


       5. A little tour through the system

       After you finish the installation as  described above, please copy  the
       files identified as the "system tour files" in the READ.ME file to your
       work disk or directory.

       Because we do  not know your  particular system  configuration, in  the
       following  examples  we  will  assume  the  worst  possible   scenario.
       Therefore, some of the  capabilities of this system  will not be  fully
       exploited.  Specifically,  we will  not invoke  the linker  or run  the
       programs from the compiler menu.


       5.1 The tour

       First, we will  look at the  unnatural case of  compiling, linking  and
       running a program that works the first time around.  Please execute the
       following commands:

            MC SIEVE /C
            M2LINK SIEVE /O

       Well, that's it! Ready to run...

            SIEVE

       Now for the more usual case:

            MC BADSIEVE

       From the compiler menu, select 'C' for compile.

       Gee, that was quick! Press RETURN to take a look at our errors...

       The cursor is now positioned at the location of the first error.  Using
       the keys Ctrl-E (find next error) and Ctrl-P (find previous error)  you
       may visit all the errors flagged by  the compiler.  All the while,  the
       editor shows the error description on the top line of the screen.  But,
       going back to the first error...

       We really confused the compiler when we mistakenly typed in '.' instead
       of '..' in  the range declaration.   Move the  cursor back (left  arrow
       key) to where  the '.' is,  type in another  '.', and  that should  fix
       that! As you will see as you type Ctrl-E, that single error caused  the
       compiler to dislike a few other things on that same line; we will  just
       ignore those errors and go on.

       Go on to the next error location (line 22).  This time, we should  have
       used a ']' but typed  '}' instead.  The  backspace key will delete  the
       offending character; now, type the ']' in its place.

       And that is that.  Shall we try to compile the program again?

       FST Modula-2                                                          7

       Press Alt-S to save the  file, Alt-Q to leave  the editor and, back  at
       the main menu, select 'C'.

       More errors?!

       These errors should have been detected during pass2 of the compiler; If
       not, you may try to fix these errors and recompile, or you may opt  for
       loading and compiling  the new  file BADSIEV1.MOD,  which contains  the
       earlier fixes.

       Going back to the editor...

       We find that we used the identifier 'cnt' which is undefined; we really
       meant to use count.  So, moving the cursor around with the cursor  keys
       and/or deleting characters  with the  backspace or  delete key,  please
       replace 'cnt' by 'count'.

       Searching for the next error...

       The next error occurred during the processing of the call to WriteCard.
       What happened here is that WriteCard requires 2 parameters, the  second
       one being the  size of  the field to  display.   So, to  fix it,  let's
       insert a comma after 'count' and some number (for example 'count,4').

       Any more errors? No, that is it...

       We now save  the file (Alt-S),  quit the editor  (Alt-Q) and  recompile
       (c).

       Now, the program should have compiled without errors.  If not, you  may
       recompile BADSIEV2.MOD instead.

       We may now quit the compiler (q).

       By now,  we  have  some program  that  has  compiled  clean  (BADSIEVE,
       BADSIEV1 or BADSIEV2).  In what  follows, we will assume all went  well
       and we have BADSIEVE.

       If you  look at  the directory,  you  will see  the new  object  module
       created as  a result  of the  previous  exercise (BADSIEVE.M2O).    The
       compiler always writes its output to a file with the extension 'M2O'.

       Let us link and test the program:

            M2LINK BADSIEVE /L

       We use the /L option so that the line number information written out by
       the compiler to the object  file will be preserved  by the linker.   We
       will need this information later.

            BADSIEVE

       Hmmm...  A runtime error at line 22.

       FST Modula-2                                                          8

       Let's see what happened...

            MC BADSIEVE

       Pick 'E' to go into the editor and, either move the cursor down to  the
       line indicated, or let the editor find it with Alt-G.

       So, what is the problem? Well, we  declared the 'flag' array to have  a
       maximum index of 8190, but 'j' got bigger than that (the FOR loop  will
       increment 'j' up to the value of 10000).

       You may fix the problem by deleting the '10000' and typing in its place
       'SIZE'.

       Recompile the program, link it, and run it.  Did it work?  Good.

       It is time for you to experiment on your own.  But, before you do  much
       more,  you  may  want   to  check  out  the   Editor  chapter  of   the
       documentation.

       FST Modula-2                                                          9


       6. The Compiler

       In the Modula-2 language, as defined by Niklaus Wirth, identifiers  may
       be used before they are declared, except when they are used in  another
       declaration (this restriction does not apply to pointers).  This forces
       the compilation process to be done in at least two passes.

       To avoid imposing unnecessary restrictions and, yet, provide reasonable
       performance, the two pass approach was selected: During the first pass,
       syntax analysis and declaration analysis are performed; The second pass
       performs the semantic analysis and code generation.

       The  compiler  has  an  integrated  text  editor.    Should  errors  be
       encountered, the editor is invoked at  the end of the current  compiler
       pass (sooner, if an error is  found during the processing of an  import
       list or if 20 errors are identified).

       The compiler also has a built in "make" processor.  A makefile must  be
       created before this  process is  invoked.   Although you  can create  a
       makefile using  the  editor, we  recommend  that you  use  the  utility
       provided for that purpose:  GENMAKE (this utility  may be invoked  from
       the compiler menu -- G).

       The compiler can generate 2 different kinds of object files as output.

       By default, M2O  (stands for  "Modula-2 Object")  files are  generated.
       This file format is  unique to this compiler,  and it is optimized  for
       our requirements and those of Modula-2.  But the user can, through  the
       use of an  environment variable (M2OUTPUT),  specify that standard  OBJ
       files are to be generated instead.


       6.1 The integrated compiler: MC

       Compiler invocation:

            MC [workModule] [/p mainModule] [/s maxIds idSpace]
                           [/c] [/e] [/m] [/d-]

       The '/p' option may be used to indicate the name of the main module  of
       the program  that you  are working  on  (the name  should not  have  an
       extension).  If  this option  is not  used, but  workModule is  entered
       without an extension, the same name is also used for mainModule.

       The '/s' option allows you to change the default sizes of the  compiler
       identifier tables.   The two arguments  specify the  maximum number  of
       different identifiers to be processed, and the total string space to be
       allocated to store these identifiers.  The default values are 2000  and
       12000, respectively.

       If you  use  of the  '/c'  command  line option,  the  compiler  starts
       compiling "workModule" immediately and,  if no errors are  encountered,

       FST Modula-2                                                         10

       will bring you  right back  to the  DOS prompt.   This  is useful  when
       running the compiler from a batch file:

            MC myprog /c

       The '/e' option will send you straight into the editor.

       The '/m' option  invokes the make  processor, which will  look for  the
       file mainModule.MAK for the dependencies list.

       The '/d-' option sets the default value of the compiler directives  $R+
       and $T+ to '-', disabling the default generation of most runtime  error
       checking.  We do not recommend  disabling the stack overflow  checking,
       and the use of '/d-' will not do it.

       The compiler always sets  the DOS errorlevel to  0 if the last  compile
       was successful (no errors); otherwise, the DOS errorlevel is set to 1.

       If the compiler is invoked without the '/c', '/e' or '/m' options,  you
       will get a screen that looks something like this:


            Modula-2 compiler, Version 3.5
            (C) Copyright 1987-1995 Fitted Software Tools. All rights
            reserved.

            Memory model in use: LARGE
            Output file format:  M2O
            Runtime environment: Modula-2

            Heap in use:                   0K
            Available Heap:              251K

            Program:

            Work module: work.mod

            Program  New   DOS      Quit
            Compile  Edit  Genmake  Make  Link  eXecute >



       The options at this point are:

         Program Specify the name of the main program module.

         New Specify another "Work module".

         DOS Invoke a new DOS shell.  At the DOS prompt, you should type  EXIT
         to return to this system.

         Quit Return to DOS.

         Compile Compile the "Work module".

       FST Modula-2                                                         11

         Edit Edit the "Work module".

         Genmake Invoke the GenMake  program, passing as argument the name  in
         Program -- and '/OBJ', if appropriate.

         Make  Recompile all  the necessary  modules  as per  the rules  of  a
         makefile (The  makefile is assumed  to have the  name in Program  and
         the extension of .MAK).

         Note: If errors are encountered during the compilation of one of  the
         modules,  the make  process  is  aborted. After  fixing  the  errors,
         select Make again.

         Link Invokes the linker (M2Link) passing along as arguments the  name
         in Program and '/L'.

         eXecute You are prompted for any arguments that you may want to  pass
         to the program; The Program is then executed.


       6.2 The freestanding compiler: M2COMP

       Compiler invocation:

            M2COMP filename [/m] [/s maxIds idSpace] [/d-]

       filename is the name of the module to  compile or, if the /M option  is
       used, the name of the makefile to process.

       The DOS errorexit is set to  0 if the compilation (make) is  successful
       and to 1 otherwise.


       6.3 The compilation process


       6.3.1 The input file

       If the module to be compiled is  already loaded into one of the  editor
       buffers, that source  is compiled.   Otherwise, the  compiler tries  to
       open the named file.


       6.3.2 The imported modules

       The compiler and the linker cooperate in assuring that all the  modules
       that refer to a  particular definition module  will have been  compiled
       against the same version of that definition module.

       To this end,  the compiler places  in the 'module  header' and  'module
       import' records of the object file a "module key".  This module key  is
       the  date  of  the  DEF  file  used  during  the  compilation  of   the
       implementation module or during the processing of the IMPORT statement.

       FST Modula-2                                                         12

       Due to this, the compiler will not  look in the editor buffers for  the
       DEF files needed to process an IMPORT  list.  These are always read  in
       from the disk.


       6.3.3 The output file

       The output from the compilation of  a main module or an  implementation
       module is a single output file, with  the same name of the source  file
       but with the  extension of  'M2O' (Modula-2  Object) --  OBJ files  are
       created instead, if the environment variable M2OUTPUT so specifies.

       The compilation of a definition module does not generate any new output
       files.   If the  compilation is  successful (no  errors), the  compiler
       simply 'touches' the source file, updating its modification time.


       6.4 A warning

       Because of the fact that the compiler uses the date of the DEF file  as
       that module's key, you may not modify  a DEF file unless you intend  to
       recompile all the modules  that use it,  nor can you  copy the file  in
       such a way that its date is not preserved.

       In particular, if you are going to be transferring your modules between
       computers, you must use some procedure  that will preserve all the  DEF
       files' dates.

       This is probably  a good  place to  point out  that, when  you use  OBJ
       files, you are not protected by this module version checking.


       6.5 Compiler directives

       Certain compiler code generation options may be set through  directives
       included in the program text.  These directives must appear immediately
       at the beginning of a comment; multiple directives may be entered in  a
       single comment by separating them by commas.  Example

             (* $S-, $R+ *)

       A '+' sets the  directive to TRUE, a  '-' sets it to  FALSE, and a  '='
       resets the directive's value to the one prior to the last '+' or '-'.

       The following compiler directives are defined:

       $A Alignment.  Default $A+.  If enabled, all new variables declared are
       aligned on a  word boundary.   Record fields are  packed (not  aligned)
       regardless of the setting of this option.

       $S Stack overflow checking.  Default  $S+.  If enabled, stack  overflow
       checking is performed  on entry to  a procedure and  when copying  open
       arrays to a procedure's local stack frame.

       FST Modula-2                                                         13

       $R Range checking.  Default $R+.  If enabled, before any assignment  is
       made to a  variable of a  subrange type, the  value to  be assigned  is
       tested against the limits of the subrange type.

       $T Array subscript and NIL pointer checking.  Default $T+. If  enabled,
       any time a subscript operation is performed on an array, the  subscript
       value is checked to  confirm that the operation  would not generate  an
       address outside the bounds of the array.  In addition, before a pointer
       is dereferenced, its value is checked for NIL.

       $L Generate line number information.   Default $L-.  If this option  is
       enabled, the compiler will include a  list of source code line  numbers
       and their corresponding object code offsets  in the output file.   This
       line number information is passed on to the .DBG file when the  program
       is linked with the /L option.


       6.6 Runtime errors

       When, during the execution of a  program, a runtime error is  detected,
       the runtime error handler  will terminate the program  and write out  a
       message indicating  the  type of  error  encountered and  its  location
       (module name, line number and PC address).


       6.6.1 Trapping runtime errors in your program

       The Library module  System provides you  with a  means of  intercepting
       runtime errors.  The following are the currently defined runtime  error
       numbers that may be passed to your error handler routine:

            0   stack overflow ($S option)
            1   range error ($R or $T option)
            2   integer/cardinal overflow (divide by zero)
            3   floating point error
            4   function did not execute a RETURN
            5   HALT was invoked
            6   CASE selector out of range


       6.7 Compiler size limits

       The following  are  the code  and  data  size limits  imposed  by  this
       compiler:

         A string  constant cannot  exceed 80 characters.   This  is also  the
         limit set for the size of any identifier.

         When  using  the  HUGE  memory  model,  each  compilation  module  is
         assigned its own  data segment, which can be up  to 64k in size.   In
         the  data segment,  the  compiler allocates  the  space for  all  the
         module's global variables and some of the module's constants.

       FST Modula-2                                                         14

         When  using  the  LARGE memory  model,  all  the  modules'  data  are
         combined, at link time, into a single data segment (64k maximum).

         The maximum size of a data structure is 65532 bytes.

         The  maximum amount  of  space allocated  for  variables local  to  a
         procedure is 32000 bytes.

         The compiler  refuses to generate  the code to  pass, in a  procedure
         call, by value, a parameter greater than 65000 bytes in size.

       The following are the compiler's internal limits:

         The maximum  number of different (namewise)  identifiers that can  be
         processed in  a single compilation  is 2000.   May be overwritten  at
         compiler invocation.

         The  total number  of  characters  in all  the  different  (namewise)
         identifiers  processed  cannot  exceed  12000  characters.    May  be
         overwritten at compiler invocation.

         No single  procedure can be  translated into more  than 10k bytes  of
         object code.

         An array  of 8k bytes is  used to keep track  of all the  initialized
         data for  a module.   This  imposes a limit  on the  total amount  of
         string, real and long constants used in the compilation module.


       6.8 The language supported

       This release of the  compiler will translate a  program written in  the
       Modula-2 language as defined by Niklaus Wirth in the 3rd edition of his
       book "Programming in Modula-2", with the exceptions noted below:

         Integer and Cardinal arithmetic overflow is not detected.

         INLINE, ASM, CLASS, INHERIT, OVERRIDE, INIT and DESTROY are  reserved
         words in this implementation.

         For those  programmers that  "grew up"  in the  Hex world,  a way  to
         define  CHAR literals  in Hex  is provided:  20X corresponds  to  the
         "space" character in ASCII.

         You  may return,  from a  function  procedure, a  value of  any  type
         (including structured types).


       6.8.1 LONGINT and LONGCARD

       This compiler implements the standard types LONGINT and LONGCARD.

       Operands of the type LONGINT or LONGCARD may appear in any  expression,
       just like INTEGER or CARDINAL.  But that is about it!

       FST Modula-2                                                         15

       Subranges of these types are not supported.

       No standard procedure,  except INC, DEC  and the ones  listed later  in
       this document will accept operands of one of these types.

       A variable of type  LONGINT or LONGCARD cannot  be used as the  control
       variable in a FOR loop.  Neither can CASE labels be of a LONG type.

       Constants of type LONGINT or LONGCARD can be coded in decimal only  and
       must be terminated by an 'L' if the value is less than 65536.  Example

            123L and 123567 are  valid LONGCARD or LONGINT constant
            -1L and -348762 are valid LONGINT constants


       6.8.2 LONGREAL

       The standard type LONGREAL is implemented.

       The rules for the use of LONGREALs are the same as for REALs.

       The types  REAL  and LONGREAL  are  not compatible,  and  no  automatic
       conversion from one type to another  is ever performed -- the  standard
       procedures SHORT  and LONG  should be  used  to convert  between  these
       types.

       Constants of type LONGREAL are no different from REAL constants.

       The type of the constant is  determined by context.  You may,  however,
       "type" a constant by the use of the SHORT or LONG procedure.

       Example:

            CONST longreal1 = LONG(1.0);


       6.8.3 Additional or augmented standard procedures


       6.8.3.1 NEW and DISPOSE -- pointer argument.

       NEW and DISPOSE have been deleted  from the language definition in  the
       3rd edition of Wirth's book.  We implement them thus:

       NEW(p) Invokes the procedure ALLOCATE, which must conform to the type:

            PROCEDURE ( VAR ADDRESS, CARDINAL )
       passing along p and the size of the object p is defined as pointing to.

       DISPOSE(p) Invokes the procedure DEALLOCATE, which must conform to  the
       type:

            PROCEDURE ( VAR ADDRESS, CARDINAL )
       passing along p and the size of the object p is defined as pointing to.

       FST Modula-2                                                         16

       The procedures ALLOCATE and DISPOSE must, therefore, be defined in  the
       module using NEW and/or  DISPOSE, or imported  from some other  module,
       like Storage.


       6.8.3.2 LONG and SHORT

            PROCEDURE LONG( INTEGER ) :LONGINT;
            PROCEDURE LONG( CARDINAL ) :LONGCARD;
            PROCEDURE LONG( REAL ) :LONGREAL;
            PROCEDURE SHORT( LONGINT ) :INTEGER;
            PROCEDURE SHORT( LONGCARD ) :CARDINAL;
            PROCEDURE SHORT( LONGREAL ) :REAL;

       LONG takes  an INTEGER,  CARDINAL or  REAL and  returns the  equivalent
       LONGINT, LONGCARD or LONGREAL, respectively.

       SHORT takes a LONGINT, LONGCARD or LONGREAL and returns the  equivalent
       INTEGER, CARDINAL or REAL, respectively.


       6.8.3.3  FLOAT and TRUNC

       With our two integer/cardinal and real  sizes, here is the behavior  of
       the TRUNC and FLOAT procedures.

            PROCEDURE FLOAT( CARDINAL ) :REAL;
            PROCEDURE FLOAT( LONGCARD ) :LONGREAL;
            PROCEDURE TRUNC( REAL ) :CARDINAL;
            PROCEDURE TRUNC( LONGREAL ) :LONGCARD;


       6.8.3.4 HALT

       The HALT  procedure  was  enhanced to  take  an  optional  argument,  a
       CARDINAL value.  The value is the runtime error number generated.

       When called without an argument, HALT generates a runtime error  number
       5.


       6.9 Objects exported by the pseudo module SYSTEM


       6.9.1.1 TYPE BYTE

       Takes 1 byte of storage.  Only assignment is defined for this type.  If
       the formal parameter of a procedure is of type BYTE, the  corresponding
       actual parameter may be of any type that takes 1 byte of storage.

       If the formal parameter of  a procedure is of  type ARRAY OF BYTE,  the
       corresponding actual parameter may be of any type.

       FST Modula-2                                                         17

       6.9.1.2 TYPE WORD

       Takes 1 word (2 bytes) of storage.  Only assignment is defined for this
       type.  If  the formal parameter  of a procedure  is of  type WORD,  the
       corresponding actual parameter may be of any type that takes 1 word  of
       storage.

       If the formal parameter of  a procedure is of  type ARRAY OF WORD,  the
       corresponding actual parameter  may be  of any  type.   Care should  be
       taken in this case, as the size  of the parameter passed is rounded  up
       to an even size.


       6.9.1.3 TYPE ADDRESS

       The type ADDRESS is compatible with all pointer types.  ADDRESS  itself
       is defined as  a POINTER  TO WORD.   In this  implementation, the  type
       ADDRESS is not compatible with any arithmetic type.  This is due to the
       fact that the Intel 8086 series processors use segmented addresses.  It
       would not be hard to  implement automatic conversions between  LONGCARD
       and ADDRESS but it is felt that this would be contrary to the spirit of
       the language,  whereby the  compiler is  not  expected to  perform  any
       "magic" tricks.  Instead, two functions are provided for that  purpose:
       FLAT and PTR.

       For compatibility with other compilers, we relaxed the above a  little.
       ADDRESS + CARDINAL and ADDRESS -  CARDINAL are legal expressions.   The
       CARDINAL is added or subtracted from the offset portion of the  ADDRESS
       and the result is still an ADDRESS.

       Also, INC and DEC  can take an  ADDRESS as their  first argument.   The
       operation is, however, performed on the  offset portion of the  ADDRESS
       only.


       6.9.1.4 SEG and OFS

       These are field definitions  for POINTER types.   If you import  these,
       you may access  the segment or  offset portions of  a pointer  variable
       using regular field selection syntax.  Example

            pointer.SEG


       6.9.1.5 PROCEDURE ADR

            ADR( designator )
       Returns the address of designator (type ADDRESS).


       6.9.1.6 PROCEDURE FLAT

            FLAT( ADDRESS )
       returns a LONGCARD "flat" address.

       FST Modula-2                                                         18

       6.9.1.7 PROCEDURE PTR

             PTR( LONGCARD )
        returns an ADDRESS corresponding to the "flat" address represented  by
       the LONGCARD.


       6.9.1.8 PROCEDURE SEGMENT

            SEGMENT( designator )
       returns the segment portion of the address of 'designator'.  Example:

            DX := SEGMENT( buffer );
       would assign to DX the segment value of ADR(buffer).


       6.9.1.9 PROCEDURE OFFSET

            OFFSET( designator )
       returns the offset portion of the address of 'designator'.


       6.9.1.10 PROCEDURE NEWPROCESS

            NEWPROCESS(p:PROC; a:ADDRESS; n:CARDINAL; VAR p1:ADDRESS)
       creates a new process whose entry point is p and workspace is at a  for
       n bytes.  p1 is the new process pointer.  This process is not activated
       until a TRANSFER to p1 is done.

       The starting  priority of  the new  process  is the  current  processor
       priority at the time NEWPROCESS is invoked (please refer to the section
       on Module Priorities).


       6.9.1.11 PROCEDURE TRANSFER

            TRANSFER( VAR p1, p2 :ADDRESS)
       suspends the current process, assigning it  to p1 and resumes p2.   The
       current process'  value  is assigned  to  p1  only after  p2  has  been
       identified; it is, therefore, okay for p1 and p2 to be the same.

       The process is resumed at the  same priority level that it was  running
       at, at the time of suspension.


       6.9.1.12 PROCEDURE IOTRANSFER

            IOTRANSFER( VAR p1, p2 :ADDRESS; intVector :CARDINAL )
       issues a TRANSFER from p1 to p2  (just the way TRANSFER does it)  after
       installing the current process for reactivation when an interrupt comes
       in through interrupt vector intVector.

       When the interrupt occurs,  the interrupt vector  is reloaded with  its
       previous value.  A TRANSFER  is done to the  I/O process (the one  that

       FST Modula-2                                                         19

       issued the  IOTRANSFER) such  that p2  now contains  the value  of  the
       process that was running when the interrupt occurred.


       6.9.1.13 ASSEMBLER

       An 8086 inline assembler is provided.  Once ASSEMBLER is imported  from
       SYSTEM, you can enter inline assembler  code by bracketing it with  the
       keywords ASM and END.

       Assembler input  is free  form.   Comments are  entered as  in  regular
       Modula-2.  Example

            loop:   CMP  BYTE [SI], 0   (*end of string?*)
                    MOV  BYTE [DI], [SI]
                    INC  SI  INC DI     (*increment pointers*)
                    JMP  loop

       The assembler  accepts all  the 8086/8088  opcode mnemonics.    Address
       operands can  be coded  in  just about  any  form acceptable  to  other
       assemblers, except that the only operator supported is '+'.

       Operand type overrides  are: WORD, BYTE,  FAR, NEAR and  are not to  be
       followed by the keyword POINTER or PTR.  Example

            label:  MOV  AX, ES:[BX,DI+5]
                    MOV  AX, ES:5[DI+BX]
                    MOV  WORD [5], 1
                    CALL NEAR [DI]
                    TEST BYTE i+2, 1

       All the mnemonics and register names must be entered in upper case.  In
       case you need to  use a Modula-2  name that conflicts  with one of  the
       assembler reserved symbols, you may precede it with a '@'.  Example

            MOV  @AX, AX

       would generate a move from register AX to variable AX.

       All modula-2 variables can generally be accessed in assembler.

       Record field names are  not accessible from  assembler.  The  assembler
       will not  automatically do  anything  for you.    For example:  if  you
       specify a VAR parameter as an operand to an instruction, you are naming
       the address of the pointer to the actual parameter.  Example

            PROCEDURE p( VAR done :BOOLEAN );
               ...
               ASM
                   LES  DI, done
                   MOV  BYTE ES:[DI], TRUE
               END;

       is the correct way of storing TRUE in done.

       FST Modula-2                                                         20

       The following types of constants may be accessed in assembler: INTEGER,
       CARDINAL, BOOLEAN, CHAR and enumeration constants.

       All labels declared inside an ASM section are local to that section  of
       code.  But labels names  cannot match some name  known in the scope  of
       the  current  procedure.    Labels  can  only  be  referenced  in  jump
       instructions.

       All jumps are optimized by the compiler.  There is, therefore, no  need
       (or capability) to  specify the  size of a  jump.   In particular,  the
       compiler will  turn a  conditional jump  out of  range into  a  reverse
       conditional jump over a far jump to the original destination.

       Remember, this is  a Modula-2 compiler,  not an  assembler! The  inline
       assembler capability  is provided  for  use in  exceptional  situations
       only.


       6.9.1.14 ASSEMBLER - 8087 support

       All the 8087 math coprocessor instructions are supported by the  inline
       assembler.  There are some restrictions, however.

       Only the following operand  types are supported by  the load and  store
       instructions: INTEGER,  LONGINT,  REAL  and LONGREAL.    You  may  not,
       therefore, load or store a value in temporary real or decimal format.

       The meaning of the "no operand" form of the arithmetic instructions was
       retained:

         FADD, FSUB, FMUL and FDIV all operate on the two top elements of  the
         8087 stack, using ST(1) as the destination and removing ST.

         FSUBR subtracts  ST(1) from ST (FDIVR  divides ST by ST(1)),  leaving
         the result in ST(1) and removing ST.

       The  2  operand   format  of  the   arithmetic  instructions  was   not
       implemented.  You  may not, therefore,  specify a destination  register
       other than ST, except in the "and pop" versions of the instructions.

       With a regular assembler, in register  to register operations, you  can
       specify the  register  that  gets the  result  of  the  operation  (the
       destination register).  By definition, the destination register is also
       the first operand of the instruction.

       With our  inline  assembler,  ST  is  always  the  destination  of  the
       operation, except in the "and pop"  form of the instructions, in  which
       case the register specified in the instruction "gets the result".

       For consistency, we decided that ST should always be the first  operand
       of the instruction, even when the "and pop" form is used.

       The meaning of FSUBP, FSUBRP, FDIVP and FDIVRP is, therefore:

       FST Modula-2                                                         21

         FSUBP  ST(1)  means  FSUBRP ST(1),ST  -> ST(1):=ST-ST(1)

         FSUBRP ST(1)  means  FSUBP  ST(1),ST  -> ST(1):=ST(1)-ST

         FDIVP  ST(1)  means  FDIVRP ST(1),ST  -> ST(1):=ST/ST(1)

         FDIVRP ST(1)  means  FDIVP  ST(1),ST  -> ST(1):=ST(1)/ST

       and ST is popped.


       6.9.1.15 INLINE

       Arbitrary inline  code may  be generated  using the  INLINE  procedure,
       which takes the form

            INLINE ( value [ ,value, value ... ] )
       where value can be a small cardinal (<= 255) literal (the 1 byte  value
       is inserted into  the code stream),  a CONSTant (2  bytes are  inserted
       into the code stream), or a  variable reference (the variable's  offset
       is inserted into the code stream).

       For example,

            INLINE (0CCH)  (* generate INT 3 as debug break point *)


       6.10 The generated object code


       6.10.1 Data type representation

         CHAR 1 byte

         INTEGER 2 bytes 2's complement

         CARDINAL 2 bytes

         LONGCARD 4 bytes

         LONGINT 4 bytes 2's complement

         BOOLEAN 1 byte (1=TRUE, 0=FALSE)

         REAL 4 bytes Intel 8087 format.

         LONGREAL 8 bytes Intel 8087 format.

         BITSET 1 word.  0 is low order bit, 15 is high order bit.

         Enumerations 1 byte

         SETs 1 to 8 words (sets of up to 256 elements)

       FST Modula-2                                                         22

         POINTERs 4 bytes in Intel 8086/88 format

         PROCEDUREs 4 bytes POINTER to procedure entry point

       Addresses are represented in the default Intel 8086 format:

            1 word byte offset
            1 word segment

       Numeric  values  are  likewise  represented  the  way  the  Intel  8086
       processor family  likes them:  low order  byte first,  high order  byte
       last.


       6.10.2 The runtime memory map

       The compiler generates code  using the "large"  or "huge" memory  model
       only.

       In the  "huge" memory  model, each  module has  its own  data and  code
       segments.

       In the "large" memory model, each module has its own code segment.  The
       entire program has one data segment.

       The linker binds  all the code  segments first, and  then all the  data
       segments.  The  stack is allocated  above the data  segments.  All  the
       remaining memory is available for the heap.

       When a program is loaded for  execution, here is what the memory  looks
       like:

       FST Modula-2                                                         23

            From low to high addresses:

            0        ----------------------------------------------
                     I  Interrupt vectors                         I
                     I  DOS                                       I
            PSP      I  Program segment prefix                    I
            PSP+100h I  Program Code segments                     I
                     I  Program Data segment(s)                   I
            StackSeg I  Stack                                     I
            HeapTop  I  Heap                                      I
                     I  ...                                       I
                     I  DOS Command (resident portion)            I
            MemTop   ----------------------------------------------

       Label names on the left are the ones exported by System.

       The system  uses interrupt  vector 192  (0C0H) at  location  0000:0300.
       Interrupt 192 is issued by a program when a runtime error occurs,  when
       HALT is invoked or when a coroutine other than the main one  terminates
       via a return.

       The first  word (offset  0) in  every code  segment contains  the  data
       segment value for that particular module (for the program, in the  case
       of the "large" memory model).


       6.10.3 Procedure calling conventions

       Procedure parameters  are pushed  into the  stack 1st  argument  first.
       Control is then transferred to the  procedure through a FAR call  (NEAR
       call  is  used  to  invoke  nested  procedures).    It  is  the  called
       procedure's responsibility  to remove  its  parameters from  the  stack
       before returning.


       6.10.3.1 Parameter passing (all except open array parameters)

       If the formal parameter of a procedure is a value parameter, the actual
       parameter is copied into the stack.

       If the formal parameter is a  variable parameter (VAR), the address  of
       the actual  parameter  is pushed  into  the stack  (first  the  segment
       portion of the address and then the offset part).


       6.10.3.2 Parameter passing (open array parameters)

       If the formal parameter is an open array, the address and HIGH value of
       the corresponding  formal parameter  are pushed  into the  stack  (HIGH
       value first, and then the address, as above).

       If the open  array parameter  is a value  parameter, the  value of  the
       actual parameter is copied into the stack on procedure entry.

       FST Modula-2                                                         24

       6.10.3.3 Returning values from a function procedure

       One byte results are returned in  AL, two byte results are returned  in
       AX, and four byte results are returned in DX:AX (DX has the high  order
       part of the result).

       LONGREALs and  arbitrary structures  are returned  in the  stack, at  a
       location reserved for  that purpose  by the  caller.   When invoking  a
       function that  returns  a  LONGREAL or  a  structured  type,  an  extra
       parameter is pushed  onto the  stack: the two  byte offset,  in the  SS
       segment, of where  to place the  result.  This  choice allows for  full
       reentrancy of the code generated.


       6.11 Module priorities

       Eight module priority levels are supported in this implementation, from
       0 (highest priority) to 7 (lowest).

       Priorities are  implemented  by  masking off,  on  the  8259  interrupt
       controller, all the interrupts at or below the current priority level.

       Because the  PC  usually runs  with  several of  the  interrupt  levels
       disabled, it is  not easy  to decide what  the interrupt  mask for  the
       value for "no priority" should be for your particular application.  The
       implementation of NEWPROCESS, therefore, assumes that you have  enabled
       all the  interrupts that  your program  will be  capable of  processing
       before you create  your processes.   The  value in  the interrupt  mask
       register of the 8259 at the time of process creation will determine the
       initial priority level of this process, once it gets started.   Because
       of this, invoking NEWPROCESS from inside  a priority module is  usually
       not what you want to do!

       Execution priorities are  changed when  entering/exiting procedures  in
       modules that have a priority specification, and during the execution of
       some form of a TRANSFER.

       We highly recommend that you study the communications program provided,
       paying particular attention to the module Kernel, for an example of how
       to use priorities with this system.

       NOTE: The compiler does not restrict the priority level specified  (any
       number will do).   You  must, therefore,  exercise care  in defining  a
       module's priority  level.    On the  other  hand,  it is  easy  to  add
       additional priority  levels  by  simply modifying  the  runtime  module
       M2Procs.


       6.12 Memory models

       In general, you may compile the same code under either the LARGE or the
       HUGE memory model.

       The only factor to consider is when using inline assembler.

       FST Modula-2                                                         25

       Under the HUGE memory model, the  compiler generates code to reload  DS
       after any invocation of an imported procedure or a VARiable  procedure.
       Under the LARGE memory  model, this is not  necessary as a single  data
       segment is  defined.   If you  write some  inline assembler  code  that
       modifies DS, please  restore it, even  if the next  thing you  do is  a
       RETurn; this way, your routine will work regardless of whether you  use
       the LARGE or the HUGE memory model.

       Under the  HUGE memory  model, access  to  external variables  is  done
       through an  indirect pointer,  whereas in  the LARGE  memory model  the
       external variable resides in  the program's ONLY  data segment and  is,
       therefore, directly accessible.

       FST Modula-2                                                         26


       7. Using OBJ files

       As long as you use this system  to write Modula-2 programs for the  DOS
       environment only, there is virtually no reason to use OBJ files.   But,
       in case you have to...

       You condition  the  compiler  to generate  OBJ  files  by  setting  the
       environment variable M2OUPUT to OBJ.

            set M2OUTPUT=OBJ

       To link OBJ files you must exit  the MC environment (or invoke the  DOS
       shell) and use some OBJ file linker.

       Also, with OBJ files, there is no module version checking done for you.
       Of course, if you  keep your ".MAK"  files up to  date, you should  not
       encounter any problems.


       7.1 GenLink

       To help you  figure out which  files need to  be linked,  we provide  a
       utility (GenLink) that generates a LINK  answer file.  The LINK  answer
       file created  by GenLink  lists all  the Modula-2  OBJ files  that  are
       required to link your program.

       Since your program,  most likely, requires  modules written in  another
       language (or you would not be using OBJ files, right?!), you will  have
       to edit the file created by GenLink to make it suitable for the job  at
       hand.

       Realizing that you will probably want to customize it, the source  code
       to GenLink is included in this software package.


       7.2 Foreign Modules

       To let  the  Modula-2 compiler  know  about modules  written  in  other
       languages, you  must  write  FOREIGN DEFINITION  modules.    A  foreign
       definition modules is a regular  definition module, with the  exception
       of the module's header, which goes like this:

            FOREIGN [C|PASCAL] DEFINITION MODULE modName;

       where the 'C' or 'PASCAL' qualifier is optional.


       7.2.1 External names

       Names of public variables and procedures in foreign modules are encoded
       in one of 3 ways:

       FST Modula-2                                                         27

         In a regular FOREIGN  (neither C nor PASCAL) module, the  identifiers
         are encoded in the object files as you enter them.

         In a FOREIGN C module, identifiers are written out preceded by a  '_'
         character.

         In a  FOREIGN PASCAL module, identifiers  are converted to all  upper
         case.

       WARNING: As currently implemented, SET and STRING constants defined  in
       a FOREIGN DEFINITION  module, cause the  compiler to generate  external
       references to these.  These "constants" should, therefore, be allocated
       space and properly initialized in the foreign module.


       7.2.2 Implementation

       FOREIGN modules are not expected  to have an initialization  procedure,
       and the compiler will  not generate these  initialization calls, as  in
       the case of regular Modula-2 modules.


       7.2.2.1 FOREIGN C modules

       When invoking a routine  defined in a FOREIGN  C module, the  arguments
       are pushed onto the stack in reverse  order, as per C's custom.   Also,
       the caller will remove the arguments off the stack upon return from the
       subroutine.

       Since C,  unlike Modula-2,  supports (?!)  the  passing of  a  variable
       number of arguments to a function, the symbol '...' may be used at  the
       end of  a  PROCEDURE parameter  list  definition to  indicate  that  an
       indeterminate number of arguments may be passed.  Example:

            PROCEDURE sum( n:INTEGER; ... ) :INTEGER;

       defines a function  that takes n  integers and returns  their sum.   It
       could, actually, be implemented in Modula-2 thus:

            PROCEDURE sum( n:INTEGER; ... ) :INTEGER;
            VAR p   :POINTER TO INTEGER;
                res :INTEGER;
            BEGIN
                res := 0;
                p := ADR(n) + 2;
                WHILE n > 0 DO
                    res := res + p^;
                    p := ADDRESS(p) + 2;
                    DEC( n );
                END;
                RETURN res;
            END sum;

       Good luck!

       FST Modula-2                                                         28

       In addition to being  useful to define functions  that take a  variable
       number of parameters,  the use of  '...' is also  a handy  (?!) way  of
       improving the odds that the arguments are passed in a form that C  will
       like.


       7.2.2.2 Parameter passing

       The form in which  the individual parameters are  passed is always  the
       same, regardless of  whether the procedure  is in a  foreign module  or
       not.  The exception is when you use '...'.

       When passing parameters that correspond to  the '...' in the  procedure
       heading, the compiler follows the default C rules: Everything is passed
       by value,  except for  arrays, which  are  passed by  reference  (their
       address, instead of their value, is pushed onto the stack).

       Some C  compilers  return structured  values  by actually  returning  a
       pointer to those values in the  DX:AX register pair, which is just  the
       way that Fitted Modula-2 returns pointers! With these, you could define

            struct someStruct cfunct()
       as

            TYPE someStructPtr = POINTER TO someStruct;
            PROCEDURE cfunct() :someStructPointer;

       7.2.3 In the real world...

       Knowing the  parameter passing  conventions used  by the  compiler  you
       should have no trouble writing assembly language modules to be  invoked
       by Modula-2.

       With the help provided (FOREIGN C and '...'), it should be easy  enough
       to interface Modula-2 to C.  But is it, really? Not quite!

       There are two main problems  that you will have  to overcome.  One,  is
       the choice  of a  suitable memory  model.   Our LARGE  memory model  is
       probably a better  choice than HUGE,  as some compilers  require DS  to
       always point to DGROUP.

       The  other  problem,  is  the  set  of  requirements  imposed  by  each
       language's runtime system.   Since we provide all  the source code  for
       our runtime, your  best bet will  probably be to  modify our system  to
       suit theirs.

       Modules that are  obvious candidates  for "adaptation"  are System  and
       Storage; In their current state, they  are virtually guaranteed to  not
       work with another vendor's runtime system, and they are a base on which
       many other library modules depend.

       FST Modula-2                                                         29

       7.3 `C' Runtime Option

       The compiler environment variable M2RUNTIME can be used to chose a  `C'
       runtime environment (you should also set M2OUTPUT = OBJ, of course).

       When the default Modula-2 runtime  environment is chosen, the  compiler
       sets the main module's  initialization part to  be the program's  entry
       point.  When the runtime environment  is set to `C', the main  module's
       initialization part is named `M2MAIN' and  it is supposed to be  called
       by your (main) C program.

       If you  use the  System.MOD  example included  in  the C  example  sub-
       directory, `M2MAIN' must be called in a manner compatible with:

            extern int _far _pascal M2MAIN(int argc,char **argv,char **envp);

            int main( int argc, char **argv, char **envp )
            {
                return M2MAIN( argc, argv, envp );
            }

       FST Modula-2                                                         30


       8. The Text Editor

       The text editor included in this package has all the features that  you
       have come to expect from a basic program editor: the ability to insert,
       delete, move, find and replace text; support for concurrent editing  of
       multiple files (as many as will fit in memory) in separate windows  (as
       many as will fit on the screen) with  the ability to copy or move  text
       from window to window.

       Although you  may load  the same  file in  two different  windows,  the
       editor will not be aware of the fact  and will treat the two copies  as
       two different files.

       The only preset limitation in the editor is that it cannot handle files
       bigger than 64k.  This decision was justified by the fact that Modula-2
       programs are supposed  to be  modular.   File load/save  speed was  the
       overriding factor here.

       All the text editor keys are defined by the user through the use of the
       EDCONFIG program.  When the editor starts, it expects to find the  file
       M2ED.CFG in the current PATH.

       M2ED.CFG will also tell the editor what display colors (attributes)  to
       use for the Status line, for Normal text and for Marked text blocks.

       Finally, M2ED.CFG also contains the default  settings for the TAB  size
       value, as  well as  whether or  not  the editor  will expand  the  tabs
       inserted into the text spaces.  Note:  Tabs present in a file will  not
       be expanded to spaces by the editor.

       To get  you started,  we provide  a M2ED.CFG  file with  the  following
       definitions:

            Cursor left                    : Left
            Cursor right                   : Right
            Cursor up                      : Up
            Cursor down                    : Down
            Previous word                  : ^Left
            Next word                      : ^Righ
            Page up                        : PgUp
            Page down                      : PgDn
            Cursor to beginning of line    : Home
            Cursor to end of line          : End
            Cursor to top of window        : ^Home
            Cursor to bottom of window     : ^End
            To beginning of file           : ^PgUp
            To end of file                 : ^PgDn
            Current line to top of window  : AltT
            Toggle insert/overtype         : Ins
            Delete character under cursor  : Del
            Delete previous character      : ^H
            Delete Current Line            : ^Y
            Delete to EOL                  : AltY

       FST Modula-2                                                         31

            Delete Word                    : ^D
            Indent Line                    : F4
            Unindent Line                  : F3
            Indent Block                   : Alt=
            Unindent Block                 : Alt-
            New file                       : AltN
            Read file                      : AltR
            Write block                    : AltW
            Save file                      : AltS
            Open window                    : ^O
            Close Window                   : ^C
            Next window                    : F2
            Previous window                : aF2
            Split screen                   : ^S
            Mark beginning of block        : F7
            Mark end of block              : F8
            Goto beginning of block        : AltB
            Goto end of block              : AltE
            Clear block marks              : AltH
            Copy block                     : AltC
            Delete block                   : AltD
            Move block                     : AltM
            Search forward                 : F5
            Search backwards               : aF5
            Replace forward                : F6
            Replace backwards              : aF6
            Global replace                 : ^F6
            Repeat last search/replace     : F1
            Goto next error                : ^E
            Goto previous error            : ^P
            Goto line                      : AltG
            Set options                    : AltO
            Redraw the screen              : ^L
            Quit                           : AltQ

       FST Modula-2                                                         32


       9. The Linker

       The linker is invoked by the command line

            M2LINK myprog [/s n] [/h n] [/o] [/l] [/v] [/swap [path]]

       where 'myprog' is the main module of the program you are creating.  The
       options are thus:

         /s n n is the size of the stack to allocate (default is 8192).

         /h  n  n  is the  amount  of  space  to  reserve  for  the  heap  (in
         paragraphs).  The default is all the free memory.

         /o invokes the optimizer.  The optimizer prevents the output, to  the
         object file, of all the procedures that are part of included  modules
         but  are  not referenced.    This  will make  your  final  EXE  files
         smaller.  This option (/o) and '/l' are mutually exclusive; The  last
         one seen on the command line is the one that takes precedence.

         /l tells  the linker to  process the line  number information in  the
         .M2O files  and include it in  the .DBG file.   This option (/l)  and
         '/o' are mutually  exclusive; The last one  seen on the command  line
         is the one that takes precedence.

         /k tells  the linker to  ignore module keys,  i.e. to  not check  for
         module  version compatibility.    This  option should  be  used  with
         extreme care.

         /v enables verbose mode (which  was the default in older versions  of
         the linker).

         /swap [path]  tells the linker  to use a  swap file.   Code  segments
         will be kept in this swap  file instead of in main memory during  the
         link process.  This allows you to link larger programs.

       The linker creates two files: the .EXE file is your executable program,
       the .DBG file is a file containing symbol information for use by  other
       utilities (see Map file generator, Profiler).

       If the /swap directive is used, a swap file is created.  You may select
       the path  (drive:directory)  where the  swap  file is  to  be  created.
       Example:

            M2LINK myprog /swap D:


       9.1 Module keys

       The module header  record and  the import  records written  out by  the
       compiler to the object file are stamped with the date of the .DEF  file
       that was processed  - this  becomes the module  key.   The linker  will
       assure that these  module keys  in the  module header  of the  imported

       FST Modula-2                                                         33

       module and  in the  import record  match; If  they do  not match,  both
       modules were not compiled using the same definition module.

       Because of the use of  module keys, it is  imperative that the date  of
       the distributed  .DEF  files  not be  modified  unless  you  intend  to
       recompile the implementation modules.

       When using OBJ files, you do not have the protection of Module keys.

       FST Modula-2                                                         34


       10. Other utilities


       10.1 Editor configurator

       This program lets you  define the keystrokes to  be used to invoke  all
       the editor commands,  the screen attributes  (colors) to  use, and  the
       default editor options.

       Invocation:

            EDCONFIG

       EdConfig presents you with  a menu from which  you may elect to  modify
       the default editor  options, the display  attributes, or configure  the
       editor commands.

       If you are creating  a new configuration file,  you must configure  the
       editor commands.

       If you chose to configure the editor commands, you will be prompted for
       a log file (default M2ED.HLP), a text file in which all of your command
       choices will be  saved (you  may print this  file to  create a  `
                                                                        `quick
       reference'
                ' card).   EDCONFIG will then prompt you for the key  sequence
       to be used for  each editor command.   For each command, EDCONFIG  will
       also give you the option of defining an alternate key sequence.

       When you select Quit, the program  will prompt you for an output  file,
       the default being 'M2ED.CFG'.


       10.2 Map file generator

       Invocation:

            DBG2MAP program_name

       Reads the .DBG file created by M2LINK and creates a DOS LINK compatible
       .MAP file.


       10.3 Make and the Makefile generator

       These utilities eliminate the burden, on the user's part, of having  to
       figure out  which  modules are  affected  and, therefore,  need  to  be
       recompiled as a result of any changes to particular definition modules.

       GENMAKE creates the .MAK file, the file with all the dependencies (this
       is the hard part)  whereas MAKE (built into  the compiler) will  insure
       that these dependencies are observed when updating the object files.

       GENMAKE Invocation:

       FST Modula-2                                                         35

            GENMAKE main_module_name [/l] [/obj]

       generates the .MAK file containing all the module dependencies for  the
       named program.  It  does this by reading  all the IMPORT statements  in
       the main module and, recursively,  generating the dependency lists  for
       all those modules.   GENMAKE will, indeed, read  all the .MOD and  .DEF
       files involved.

       The /l option instructs  GenMake to include  the directories listed  in
       the environment variable M2LIB in its search path.  Otherwise, only the
       modules in the current directory will be taken into consideration  when
       creating the makefile.

       The /obj option tells  GenMake that you will  be using the compiler  to
       generate OBJ files instead of M2O files.

       MAKE invocation: see the sections on MC and M2COMP.

       MAKE will  invoke  the  compiler  as needed  to  assure  that  all  the
       dependencies in the make file  are observed.  MAKE  is dumb in that  it
       will just  run through  the makefile  sequentially.   It was  GENMAKE's
       responsibility to  see that  the dependencies  are listed  in a  proper
       sequence.  Please keep this in mind if you should edit a makefile!


       10.4 M2O file decoder

       Invocation:

            DECODE module_name

       Decodes a .M2O file sending the output to the terminal.

       FST Modula-2                                                         36


       11. The Library Modules

       For complete information on what each library module provides, as  well
       as its proper usage, please refer to the .DEF files.

       In addition, the source code of all the library modules is included  in
       the registered version of this product.


       11.1 Release 3 libraries

       We have  not created  any source  level incompatibility  with  previous
       library modules, that we are aware of.  But you will have to  recompile
       all your programs, as they will not link with the new library modules.

       FST Modula-2                                                         37


       12. The runtime support system

       The library module System contains the runtime system's  initialization
       code.  In  particular, special interrupt  vectors are  loaded with  the
       addresses of routines (also in System) that will handle runtime  errors
       and other  abnormal  program terminations.    In addition,  the  module
       Storage depends  on System  doing its  stuff --  setting the  HeapBase,
       HeapTop and MemTop variables.

       Three other special  modules are included  in this  package.   M2Reals,
       M2Longs and  M2Procs.    These modules  provide  support  for  specific
       language features.

       M2Reals handles all the REAL  and LONGREAL arithmetic and  conversions.
       M2Reals checks for the presence of  an 8087 math co-processor and  uses
       it, if found.

       M2Longs handles LONGINT and LONGCARD arithmetic.

       M2Procs provides the coroutine support.

       FST Modula-2                                                         38


       13. Modula-2 Syntax

       ident = letter {letter | digit}.
       number = integer | real.
       integer = digit {digit} | octalDigit {octalDigit} ("B"|"C") |
                 digit {hexDigit} "H".
       real = digit {digit} "." {digit} {ScaleFactor}.
       ScaleFactor = "E" ["+"|"-"] digit {digit}.
       hexDigit = digit | "A" | "B" | "C" | "D" | "E" | "F".
       digit = octalDigit | "8" | "9".
       octalDigit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7".
       string = "'" {character} "'" | '"' {character} '"' .
       qualident = ident {"." ident}.
       ConstantDeclaration = ident "=" ConstExpression.
       ConstExpression = expression.
       TypeDeclaration = ident "=" type.
       type = SimpleType | ArrayType | RecordType | SetType |
              PointerType | ProcedureType.
       SimpleType = qualident | enumeration | SubrangeType.
       enumeration = "(" IdentList ")".
       IdentList = ident {"," ident}.
       SubrangeType = [ident] "[" ConstExpression ".." ConstExpression "]".
       ArrayType = ARRAY SimpleType {"," SimpleType} OF type.
       RecordType = RECORD FieldListSequence END.
       FieldListSequence = FieldList {";" FieldList}.
       FieldList = [IdentList ":" type |
                   CASE [ident] ":" qualident OF variant {"|" variant}
                   [ELSE FieldListSequence] END].
       variant = [CaseLabelList ":" FieldListSequence].
       CaseLabelList = CaseLabels {"," CaseLabels}.
       CaseLabels = ConstExpression [".." ConstExpression].
       SetType = SET OF SimpleType.
       PointerType = POINTER TO type.
       ProcedureType = PROCEDURE [FormalTypeList].
       FormalTypeList = "(" [ [VAR] FormalType
                        {"," [VAR] FormalType} ] ")" [":" qualident].
       VariableDeclaration = IdentList ":" type.
       designator = qualident {"." ident | "[" ExpList "]" | "^"}.
       ExpList = expression {"," expression}.
       expression = SimpleExpression [relation SimpleExpression].
       relation = "=" | "#" | "<" |"<=" | ">" | ">=" | IN.
       SimpleExpression = ["+"|"-"] term {AddOperator term}.
       AddOperator = "+" | "-" | OR.
       term = factor {MulOperator factor}.
       MulOperator = "*" |"/" | DIV | MOD | AND.
       factor = number | string | set | designator [ActualParameters] |
               "(" expression ")" | NOT factor.
       set = [qualident] "{" [element {"," element}] "}".
       element = expression [".." expression].
       ActualParameters = "(" [ExpList] ")" .
       statement = [assignment | ProcedureCall |
                   IfStatement | CaseStatement | WhileStatement |
                   RepeatStatement | LoopStatement | ForStatement |

       FST Modula-2                                                         39

                   WithStatement | EXIT | RETURN [expression] ].
       assignment = designator ":=" expression.
       ProcedureCall = designator [ActualParameters].
       StatementSequence = statement {";" statement}.
       IfStatement = IF expression THEN StatementSequence
                     {ELSIF expression THEN StatementSequence}
                     [ELSE StatementSequence] END.
       CaseStatement = CASE expression OF case {"|" case}
                       [ELSE StatementSequence] END.
       case = [CaseLabelList ":" StatementSequence].
       WhileStatement = WHILE expression DO StatementSequence END.
       RepeatStatement = REPEAT StatementSequence UNTIL expression.
       ForStatement = FOR ident ":=" expression TO expression
                      [BY ConstExpression] DO StatementSequence END.
       LoopStatement = LOOP StatementSequence END.
       WithStatement = WITH designator DO StatementSequence END .
       ProcedureDeclaration = ProcedureHeading ";" block ident.
       ProcedureHeading = PROCEDURE ident [FormalParameters].
       block = {declaration} [BEGIN StatementSequence] END.
       declaration = CONST {ConstantDeclaration ";"} |
                     TYPE {TypeDeclaration ";"} |
                     VAR {VariableDeclaration ";"} |
                     ProcedureDeclaration ";" | ModuleDeclaration ";".
       FormalParameters =
                "(" [FPSection {";" FPSection}] ")" [":" qualident].
       FPSection = [VAR] IdentList ":" FormalType.
       FormalType = [ARRAY OF] qualident.
       ModuleDeclaration =
           MODULE ident [priority] ";" [import] [export] block ident.
       priority = "[" ConstExpression "]".
       export = EXPORT [QUALIFIED] IdentList ";".
       import = [FROM ident] IMPORT IdentList ";".
       DefinitionModule = DEFINITION MODULE ident ";"
                          {import} {definition} END ident "." .
       definition = CONST {ConstantDeclaration ";"} |
                    TYPE {ident ["=" type] ";"} |
                    VAR {VariableDeclaration ";"} |
                    ProcedureHeading ";".
       ProgramModule = MODULE ident [priority] ";" {import} block ident ".".
       CompilationUnit = DefinitionModule | [IMPLEMENTATION] ProgramModule.

       FST Modula-2                                                         40


       14. OO Extensions

       FST Modula-2  adds extensions  to standard  Modula-2, intended  to  add
       support for object oriented programming (OOP), through the introduction
       of classes.

       These extensions to Modula-2 should not, of course, be used if  program
       portability to other environments is desired.


       14.1 Highlights of the implementation

       Readers already familiar with OOP terminology will be keen to note  the
       highlights of this implementation.  The FST extensions to Modula-2  are
       a small, but well  thought out, selection of  the many ideas that  have
       been put forward in recent years:

         OOP features  are supported  by the use  of five  new reserved  words
         (CLASS,  INHERIT, OVERRIDE,  INIT and  DESTROY), three  new  standard
         identifiers  (SELF,  SUPER  and  MEMBER),  and  the  Objects  library
         module.

         Class  definitions may  appear  in definition  modules,  leaving  the
         details of the implementations to implementation modules.

         Class implementations  may extend  the class  definition, adding  new
         attributes and methods to the original definition.

         Only single inheritance is allowed in this release.

         Methods declared in a class definition are virtual.

         Methods declared in a class implementation are static.

         All objects  are dynamic,  i.e. you  must create  all objects  before
         using them.  There is no automatic garbage collection, i.e. you  must
         explicitly dispose of objects when no longer required.   Effectively,
         classes are  a special kind  of pointer type,  but that fact  remains
         largely hidden from the user.

         Class  implementations  may  define  object  constructor  (INIT)  and
         destructor (DESTROY) methods  that are invoked automatically when  an
         object is created (by NEW) or destroyed (by DISPOSE).

         Local classes  may be implemented  within program and  implementation
         modules.    In such  cases,  the  class implementation  is  also  its
         definition (in an analogous  manner to local modules not requiring  a
         separate definition).

         Classes  cannot,  however,  be  implemented  inside  local   (nested)
         modules.

       FST Modula-2                                                         41

       14.2 Classes, attributes, methods, inheritance and polymorphism

       This section aims to give a very brief introduction to the  terminology
       and ideas of object oriented programming (OOP) for new readers.

       Support for an "object" oriented paradigm of programming in Modula-2 at
       the lowest  level consists  of allowing  you to  define object  "types"
       (record,  arrays,  ...)  and   to  write  procedures  that   manipulate
       parameters of those classes of objects.  Thus we might have

            TYPE
             Gender = (male,female);
             Person = RECORD
                        name : ARRAY [0..40] OF CHAR;
                        sex  : Gender;
                      END;

            PROCEDURE IsMale (Human : Person) : BOOLEAN;
             BEGIN
               RETURN Human.sex = male
             END IsMale;

       In Modula-2  in particular,  it is  common to  find all  these  related
       definitions encapsulated in a module.   Indeed, one can go further  and
       hide the details  of the type  within the  implementation; the  library
       module Windows, for example, defines an opaque type Window and a  bunch
       of procedures  that  operate  on  objects  of  that  type  (OpenWindow,
       SelectWindow, ... ).

       Once one has defined a type like Person, one can go on to use it in the
       definition of other types

            TYPE
             Programmer = RECORD
                who : Person;
                favoriteLanguage : ARRAY [0..10] OF CHAR;
             END;

       and  introduce  other  procedures  to  manipulate  parameters  of   the
       Programmer type.  But the awkward fact remains that, while in real life
       programmers are also persons, in Modula-2 variables of Programmer  type
       are  inherently   incompatible   with   variables   of   Person   type.
       Furthermore, if one wishes to hide the details of the types by defining
       them as opaque types, the mechanisms allowed depend heavily on  awkward
       pointer manipulations.

       Thus, it can be  said that the methods  traditionally used in  Modula-2
       for program decomposition,  while reducing  the complexity  of a  total
       system, often tend to produce building blocks that are specific to  the
       program at hand, and are reusable  for other purposes only after a  lot
       of extra effort has been put into modifying them.  Re-using  components
       often requires many textual changes, which can lead to an entire family
       of "similar" components that must all be managed and maintained.

       FST Modula-2                                                         42

       Enter the new OOP ideas!   Better support for the paradigm is  realized
       by extending the idea of a RECORD type to the idea of a CLASS type.

       A Class  is  a  new user  defined  type.   A  class  defines  both  the
       "attributes" (fields)  and "methods"  (procedures) of  the  "instances"
       (objects) of that class; a  CLASS declaration combines the  declaration
       of an object's  structure with the  declaration of  all the  procedures
       (methods, in OOP terminology) that deal  with that kind of object.   An
       OOPed version of FileSystem might be introduced along these lines:

            DEFINITION MODULE OOPFileSystem;
              FROM SYSTEM IMPORT WORD;
              TYPE
                Response = ( done, notdone );
                IOMode   = ( read, write, io );
              CLASS File;
                id   :INTEGER;
                res  :Response;
                eof  :BOOLEAN;
                mode :IOMode;
                PROCEDURE Lookup(
                    filename :ARRAY OF CHAR; new :BOOLEAN
                    );
                PROCEDURE ReadWord( VAR w :WORD );
                PROCEDURE WriteWord( w :WORD );
                (*...*)
              END File;
            END OOPFileSystem.

       This code, actually, gives the class  definition rather than the  class
       implementation  --  the  implementation   would  be  elaborated  in   a
       corresponding implementation module.   As usual,  the definition  gives
       all we need to know to  be able to start using  the class -- a  program
       using OOPFileSystem might be coded like:

            FROM OOPFileSystem IMPORT File;
              VAR
                f :File;
                w :WORD;
              BEGIN
                NEW(f); (* create an object of the File class *)
                f.Lookup( "myfile.txt", FALSE ); (* open file *)
                IF f.res = done THEN
                    f.ReadWord( w );
                ...

       Notes:

         We  have coded  f.ReadWord(w)  instead  of ReadWord(f,w).    This  is
         called  "sending a  message  to the  object".    What used  to  be  a
         parameter (f)  now becomes the  recipient of the  message -- we  send
         the message Lookup to the object f, which is "intelligent" enough  to
         know how to react to the message and do something to itself.

       FST Modula-2                                                         43

         We had to go further than  just declare an instance  f  of the  class
         File;  we had  to create  the instance  by calling   NEW(f),   in  an
         analogous way to that used  for creating dynamic variables.  This  is
         a feature  of the  FST dialect --  other languages  do this  creation
         automatically once the instances (variables) are declared.

         In FileSystem.File there is a field (fdptr) of an opaque type,  which
         is used  to hold implementation  specific information.   This is  not
         needed in  OOPFileSystem.File because, as  we shall see,  we can  add
         the needed fields to File in its implementation.

       So far this does not seem to have bought us much other than a syntactic
       change, writing code  like f.ReadWord(w)  in place  of ReadWord(f,  w).
       However, a very real advantage comes from being able to extend classes.
       Consider a class definition for a Person

            TYPE Gender = (unknown,male,female);
            CLASS Person;
              name    :ARRAY [0..40] OF CHAR;
              sex     :Gender;
              PROCEDURE isMale() :BOOLEAN;
            END Person;

       Notice that the  method takes  no parameter  (as we  have implied,  the
       method will "know"  about the object  already), in  distinction to  the
       style used in writing traditional procedures:

            IF Queen.isMale() THEN SomethingWrong END;

       Programmers are  also  people, but  they  have further  attributes  and
       methods for dealing with them:

            TYPE LanguageName = ARRAY [0..10] OF CHAR;
            CLASS Programmer;
              INHERIT Person;
              favoriteLanguage :LanguageName;
              PROCEDURE isSmart() :BOOLEAN;
            END Programmer;

            VAR Hacker  :Programmer;

       Note the INHERIT clause: an object of the Programmer class is going  to
       have all the attributes of a Person, and be subjectable to the  methods
       that can be applied to a Person -- as well as having new properties  of
       its own.  The Programmer class is said to be based on the Person class,
       or to be  a "sub-class" or  descendant of the  Person "super-class"  or
       ancestor.

            IF Hacker.isSmart() THEN
                IF Hacker.isMale() THEN Employ(Hacker)
                ELSE Exploit(Hacker)
                END
            END;

       FST Modula-2                                                         44

       This implies that an object of  the Programmer class is compatible,  to
       some degree, with an object of a Person class -- even though it appears
       to be "bigger"!   The procedures (or methods)  like Employ and  Exploit
       might take parameters of the Programmer class; they might equally  well
       take parameters of the Person class.

       This guaranteed compatibility  of derived  data types  with their  base
       types lies behind  the realization of  an idea  known as  polymorphism.
       Some variables must be able to assume values of different (but related)
       data types at run time and,  in the course of operations with  objects,
       there must be the possibility of  determining at run time the  concrete
       actions to be executed (depending on  the current dynamic data type  of
       the object).

       All of this represents  quite a departure from  the very strict  typing
       philosophy of Modula-2.

       As we have already noted, activating an operation on an object is often
       termed "sending  a  message to  the  object".   The  object  reacts  by
       executing  a  method.    The  assignment  of  methods  to  messages  is
       determined for each class by the  respective class definition, and  the
       effect of  sending  a message  differs  from procedure  invocations  in
       conventional programming languages in  that the determination of  which
       method is to be executed can (and often must) occur at run time, rather
       than at compile time.  This  is discussed further in the section  "Data
       Structures with Classes".


       14.3 Defining classes in definition modules

       Time for some specifics that apply to this compiler.

       A Class  definition  may  appear  in  a  definition  module,  where  it
       specifies the format of the data to be allocated when an object of that
       class is  instantiated, and  a set  of  methods (procedures)  that  are
       available to manipulate that object.

       A class  definition is  analogous to  a type  definition; the  extended
       syntax for definitions in a definition module is described by

            definition = "CONST" {ConstantDeclaration ";"} |
                         "TYPE" {ident ["=" type] ";"} |
                         "VAR" {VariableDeclaration ";"} |
                         "CLASS" ClassDefinition ";" |
                         ProcedureHeading ";" .

            ClassDefinition =
                    className ";"
                    [ "INHERIT" someClassName [ "OVERRIDE" IdentList ] ";" ]
                    { AttributeDeclaration }
                    { MethodDefinition }
                    "END" className .

            AttributeDeclaration =  VariableDeclaration ";"  .

       FST Modula-2                                                         45


            MethodDefinition = ProcedureHeading .
            className = ident .
            someClassName = qualident .

       An example of a Definition Module incorporating CLASS definitions is:

            DEFINITION MODULE People;
              TYPE Gender = (unknown,male,female);
              CLASS Person;
                name    :ARRAY [0..40] OF CHAR;
                sex     :Gender;
                PROCEDURE isMale() :BOOLEAN;
              END Person;

              TYPE  LanguageName = ARRAY [0..10] OF CHAR;
              CLASS Programmer;
                INHERIT Person;
                favoriteLanguage :LanguageName;
                PROCEDURE isSmart() :BOOLEAN;
              END Programmer;
            END People.

       Notes:

         The  attributes  are  effectively  the  data  fields  of  the   class
         (object).

         Methods are  effectively procedures and,  therefore, defined as  such
         in an  analogous way to defining  other procedures exported from  the
         module.

         The  methods  are  technically  known  as  "virtual  methods".     An
         implementation  of each  one must  be declared  in the  corresponding
         implementation   module;   however,  this   implementation   may   be
         overridden in classes derived from the one being defined.

         A class is really a kind  of pointer, so an attribute of a class  may
         be defined in terms of the  class itself.  This is useful in  setting
         up  linked structures  (For examples  of this,  please refer  to  the
         section  "Data  Structures  with  Classes"  and  the  Lists   example
         supplied with the compiler).

         FST Modula-2 only supports single inheritance -- a subclass can  only
         inherit directly from its one immediate superclass.


       14.4 Implementing classes in implementation modules

       A class defined in a definition  module must, of course, be  elaborated
       (its  methods  fully  declared)  in  the  corresponding  implementation
       module.  An implementation  module or program  module may also  declare
       local classes.

       FST Modula-2                                                         46

       The syntax of declarations in this  extension of Modula-2 is  described
       by

            declaration =
                CONST {ConstantDeclaration ";"} |
                TYPE {TypeDeclaration ";"} |
                VAR {VariableDeclaration ";"} |
                "CLASS" [ ClassImplementation | ClassDeclaration ] ";" |
                ProcedureDeclaration ";" |
                ModuleDeclaration ";".

            ClassImplementation =  (* in implementation modules *)
                className ";"
                { AttributeDeclaration }
                { MethodImplementation }
                [ "INIT" StatementSequence ]
                [ "DESTROY" StatementSequence ]
                "END" className .

            ClassDeclaration = (* used for local classes *)
                className ";"
                [ "INHERIT" someClassName [ "OVERRIDE" IdentList ] ";" ]
                { AttributeDeclaration }
                { MethodImplementation }
                [ "INIT" StatementSequence ]
                [ "DESTROY" StatementSequence ]
                "END" className .

            MethodImplementation = ProcedureDeclaration .

       Notice that  a ClassDeclaration  for a  local  class may  inherit  from
       another class;  however, in  a ClassImplementation  that completes  the
       elaboration of  a class  defined in  a definition  module, the  INHERIT
       clause must not be repeated.

       An example of an Implementation Module incorporating CLASS  definitions
       is:

            IMPLEMENTATION MODULE People;
              FROM Strings IMPORT CompareStr, Assign;

              CLASS Person;    (* a class implementation *)
                PROCEDURE isMale() :BOOLEAN;
                  BEGIN
                    RETURN sex = male;
                  END isMale;
                INIT
                  name := "";
                  sex := unknown;
             END Person;

              CLASS Programmer;  (* a class implementation *)
                PROCEDURE isSmart() :BOOLEAN;
                  BEGIN

       FST Modula-2                                                         47

                    RETURN CompareStr(favoriteLanguage,"Modula-2") = 0;
                  END isSmart;
                INIT
                  favoriteLanguage := "?";
              END Programmer;

              CLASS Vendor;      (* a local class declaration *)
                INHERIT Programmer;
                BusinessAddress : ARRAY [0..40] OF CHAR;
                PROCEDURE GetAddress (VAR Address : ARRAY OF CHAR);
                  BEGIN
                    Assign(BusinessAddress, Address)
                  END GetAddress;
                INIT
                  BusinessAddress := "PO Box 867403, Plano, Texas"
              END Vendor;
            END People.

       Notes:

         Local classes  may be declared in  program modules or  implementation
         modules only at the outermost level,   i.e. they may not be  declared
         in local (nested) modules.

         In a class implementation,  further attributes may be added to  those
         already  defined  in  the class  definition  as  it  appears  in  the
         definition module.

         In a  class implementation,  further methods  may be  added to  those
         introduced in  the class  definition.   These are  known as  "static"
         methods.

         Direct access  to the attributes exported  from the class  definition
         is  allowed, but  should be  avoided --  rather provide  methods  for
         accessing the attributes safely.

         Inside  a   method,  you  have   full  access  (with   no  need   for
         qualification)  to the  fields of  the object  that you  are  dealing
         with.

         When a  method is called, the  reference to the  object on behalf  of
         which the  method will operate  forms part of  the message.   If  you
         need  it,  you  may  access  this  object's  reference  through   the
         predefined object handle SELF.


       14.5 SELF

       SELF is an identifier, pre-declared in every method, that is associated
       with the object  instance on behalf  of whiuch the  method was  invoked
       (the object that the message was sent to).

       In isSmart, above, we could have used SELF

       FST Modula-2                                                         48

            CompareStr(SELF.favoriteLanguage,"Modula-2") = 0;
       but that is not necessary in this case.

       You would, typically,  use SELF  when you  want to  pass your  object's
       reference as a  parameter to a  method of another  class.   We show  an
       example of this in our Lists example.


       14.6 SUPER

       SUPER, like SELF, is pre-declared in every method.  SUPER refers to the
       parent of the  class that  the method  is defined  in, and  is used  to
       invoke a method that is overriden in the current class.

       SUPER is useful in  cases where you  do not really  want to override  a
       method of a parent  class but you want  to `
                                                   `extend'
                                                          '  it instead.   You
       use it something like:

            CLASS Child;

                INHERIT Parent OVERRIDE printSelf;

                PROCEDURE printSelf;
                BEGIN
                    SUPER.printSelf;    (* print parent's stuff *)
                    (* print my extensions *)
                END printSelf;

            END Child;

       14.7 Object instantiation, creation and destruction

       An object handle holds a reference (call  it a pointer if you must)  to
       an object instance.

       You declare an  object handle  just as  you would  declare a  variable,
       using for its type the name of the class (of objects) that this  handle
       will refer to.  For example:

            VAR myBoss :Person;

       However, since in this implementation all objects are "dynamic", before
       you can use an object  you must instantiate (create)  it via a call  to
       the standard procedure NEW, passing NEW the handle as a parameter (This
       is somewhat analogous to using NEW to allocate storage for  dynamically
       created variables  accessed  via  other pointers  in  Modula-2).    For
       example

            NEW( myBoss );

       NEW invokes the procedure ALLOCATEOBJECT, which allocates the necessary
       amount of  storage to  hold  the object's  data  and then  invokes  the
       object's  initialization   code   defined   for  the   class.      This

       FST Modula-2                                                         49

       initialization code is defined by the statement sequence following INIT
       in the class implementation.

       Once you no  longer need  the object, you  may get  rid of  it via  the
       standard procedure DISPOSE.  For example

            DISPOSE( myBoss );

       DISPOSE invokes the procedure  DEALLOCATEOBJECT, which deallocates  the
       necessary amount of storage  to hold the  object's data after  invoking
       the object's destruction code defined for the class.  This  destruction
       code is  defined by  the statement  sequence following  DESTROY in  the
       class implementation.

       Implementations of the  procedures ALLOCATEOBJECT and  DEALLOCATEOBJECT
       are found in the  library module Objects (or  you can write your  own).
       Typically, an OOP module imports from Objects, of course.

       It may be desirable to have multiple object handles reference the  same
       object.  We  can easily do  this by assigning  the value  of an  object
       handle to another object handle.

            objHandle2 := objHandle1;

       Please note that we are assigning object handles, not making a copy  of
       the object itself.  If  we 'DISPOSE(objHandle1)', objHandle2 will  hold
       an obsolete  handle.   This  is  analogous to  the  "dangling  pointer"
       situation that  can arise  with other  dynamically allocated  variables
       accessed through pointers in the familiar way.


       14.8 Object compatibility

       The possibility  of  assigning one  object  handle to  another,  or  of
       passing objects as parameters  to procedures or  methods, brings us  to
       the question of compatibility rules between objects.


       14.8.1 Assignment compatibility

       In FST Modula-2 two object handles are assignment compatible

         if they are of the same class type, or

         if the type of the source  operand is a subclass (descendant) of  the
         type of the destination operand.

       For example, consider the declarations

            VAR
                myBoss :Person;
                me     :Programmer (* inherits from Person *);

       me is assignment compatible with myBoss, but not the other way around:

       FST Modula-2                                                         50

            myBoss := me;       (* right! *)
            me := myBoss;       (* wrong *)

       The simple way to remember this is  that an assignment is legal if  the
       source can completely fill  the destination (the  fields that are  left
       over are just "truncated"),  but not the reverse  (as that would  leave
       fields dangerously undefined).

       In point of fact, classes in FST Modula-2 are really clever pointers to
       record types, rather than clever record types (as they are in  Oberon).
       So, the assignment is really of  one machine address to a  destination,
       rather than  copying  a  collection of  field  values,  and  the  above
       explanation  is  not  quite  honest   (In  other  OOP  languages,   the
       distinctions between  classes and  subclasses and  between pointers  to
       classes and pointers to subclasses are much more complex.).


       14.8.2 Expression compatibility

       In FST Modula-2, the only operators that may appear between two  object
       operands are the Boolean test for  equality and for inequality.   There
       is an asymmetry, however.  Given  the declarations above, we may  write
       code like

            IF myBoss = me THEN Rejoice END;
            IF (you = me) OR (me = you) THEN SomethingFunny END;

       but not

            IF me = myBoss THEN WeShallNeverKnow END  (* illegal *);

       the type of the right operand may be a subclass of the type of the left
       operand, but not vice versa.  This makes sense -- since one could never
       have made an assignment like me := myBoss there is no way that me could
       become equal to myBoss anyway.   But given that FST objects are  really
       implemented  as  pointers  to  super  records,  such  comparisons   are
       misleading in any case!


       14.8.3 Parameter compatibility

       Compatibility is needed between the formal  and actual parameters of  a
       method or procedure.

       In FST  Modula-2, an  object handle  used as  an actual  parameter,  is
       compatible with  a  formal parameter,  passed  either by  value  or  by
       reference, if

         the formal and actual parameters are of the same class type, or

         the class type of the actual parameter is a subclass (descendant)  of
         the class of the formal parameter.

       This is effectively the same rule as for assignment compatibility.

       FST Modula-2                                                         51

       Since we usually  have methods  incorporated into  classes, this  still
       makes sense.  For example, the  class Person has methods that know  how
       to deal  with objects  of that  class.   The subclass  Programmer  adds
       methods that know how to deal with the Programmer extensions to Person.
       If one were to apply a Programmer method to a simple Person, the method
       would very likely fail because it  tried to access non-existent  fields
       of the  Person structure  (for example,  isSmart  needs access  to  the
       favoriteLanguage attribute).  But a  Person method (like isMale)  would
       have no trouble handling a Programmer, whose structure includes all the
       Person information.

       Do not forget  that the method  Person.isMale is also  a method of  the
       class Programmer!


       14.9 The MEMBER function

       Clearly situations will arise where a procedure or method needs to know
       exactly what type its actual parameter  is, since the actual  parameter
       may be of a type that is a subclass of the formal parameter.

       Hmmm.  That's a nice point.   Formal parameters of dynamically  varying
       types?  Well, yes, you can almost think of it like that!

       FST Modula-2 has a standard function called MEMBER:

            MEMBER( objectHandle, className )

       MEMBER returns TRUE

         if Object is of the class denoted by ClassName or

         if  ClassName is  a  superclass (ancestor)  of  the actual  class  of
         Object.

       To use  the function  MEMBER, you  must  import MEMBEROBJECT  from  the
       library Objects (or write your own version of MEMBEROBJECT).


       14.10 Compatibility between objects and other types

       Since in FST Modula-2 classes  are, really, disguised pointers,  object
       handles are  compatible with  NIL and  with SYSTEM.ADDRESS.    However,
       direct use of this last feature should probably be minimized.


       14.11 Pointers to classes

       The fact that objects  are really a disguised  form of pointer is  very
       useful when we want  to try to create  base classes that handle  linked
       structures (because attributes of  a class may be  defined as being  of
       the class type itself, just as nodes pointed to in a traditional linked
       structure may have fields that point to other nodes in the same way).

       FST Modula-2                                                         52

       Of course this does not prevent us from making declarations like

            CLASS BaseClass;
                x, y :AttributeType;
            END BaseClass;

            CLASS SubClass;
                INHERIT BaseClass;
                a, b :AnotherAttributeType;
            END SubClass;

            TYPE
                BasePtr = POINTER TO BaseClass;
                SubPtr  = POINTER TO SubClass;
                VAR
                    B  :BasePtr;
                    S  :SubPtr;

       In other OOP languages this may be necessary for the creation of linked
       structures.  In such languages (but not in FST Modula-2) SubPtr is then
       usually taken  to be  an  extended pointer  type  of BasePtr,  just  as
       SubClass is an extension of  BaseClass, and then special  compatibility
       rules might exist between variables and parameters of these two  types;
       for example we might try to write code like

            B := S   (* legal, but not in FST *);
            S := B   (* always illegal *);

       and we might be able to pass S by value (but not by reference, that  is
       as a VAR parameter) to a procedure whose corresponding formal parameter
       is of type BasePtr.


       14.12 Complete example



       Here is a simple example to illustrate the ideas of the last  sections:
       using the classes declared in People.

            MODULE UsePeople;

            FROM InOut   IMPORT WriteString, WriteLn;
            FROM People  IMPORT Person, Programmer, Gender;
            FROM Objects IMPORT ALLOCATEOBJECT, DEALLOCATEOBJECT,
                                MEMBEROBJECT;

            VAR
             someone         :Person;
             smartProgrammer :Programmer;

            PROCEDURE DisplaySkills ( VAR Student :Person );
            VAR
              P  :Programmer;

       FST Modula-2                                                         53

            BEGIN
              IF Student.isMale()
                THEN WriteString( "He " )
                ELSE WriteString( "She " )
              END;
              IF MEMBER( Student, Programmer )
                THEN
                  WriteString( "is a programmer " );
                  P := Programmer( Student );
                  IF P.isSmart()
                    THEN WriteString( "and has learned a fine language" )
                    ELSE WriteString( "but never learned Modula-2" )
                  END
                ELSE WriteString( "has managed to avoid programming ")
              END;
              WriteLn;
            END DisplaySkills;

            BEGIN
             NEW( smartProgrammer );
             smartProgrammer.sex := female;
             DisplaySkills( smartProgrammer );

             NEW( someone );
             someone.sex := male;
             DisplaySkills( someone );

             smartProgrammer.favoriteLanguage := "Modula-2";
             DisplaySkills( smartProgrammer );

             someone := smartProgrammer;
             IF MEMBER( someone, Programmer ) & smartProgrammer.isSmart()
               THEN
                 WriteString( "What a smart programmer to use Modula-2!" );
                 WriteLn;
             END;
            END UsePeople.

       Within the  DisplaySkills  procedure we  may  apply the  isMale  method
       regardless of the apparent type of the formal parameter.  We may  apply
       the isSmart  method only  to  objects known  to  be of  the  Programmer
       subclass of  Person.   Such an  object  might be  passed as  an  actual
       parameter to Student, so the type test

            IF MEMBER( Student, Programmer )

       is used to decide on the action.  Notice, however, that we have to make
       a typecast involving the formal parameter Student to the local variable
       P, because we are not in a position  to apply the isSmart method to  an
       object of type Person  directly (even though we  know that at  run-time
       things will be  all right, at  compile-time the compiler  is trying  to
       catch all type-breaking that  it can).  This  type cast is quite  safe,
       because all objects are really pointers.

       FST Modula-2                                                         54

       Notice, by  contrast,  how  the  MEMBER  function  is  applied  without
       subsequent typecasting in the main program in the line

            IF MEMBER( someone, Programmer ) &
               smartProgrammer.isSmart() THEN

       The output from this program would be

            She is a programmer but never learned Modula-2
            He has managed to avoid programming
            She is a programmer and has learned a fine language
            What a smart programmer to use Modula-2!

       (A programmer coming up with code like that above would be  justifiably
       proud  of  the  definition  of  a  smart  programmer.    However,   the
       programmer's employer, who probably is one of the great unwashed who do
       not know Modula-2, might  not have liked  it.  After  all, how smart  a
       programmer is has more to do with the tools that he or she chooses  and
       how the tools are used, rather than simply relying on the knowledge  of
       a favourite language! Hmmm...)


       14.13 Virtual methods

       Earlier we  mentioned  that, in  FST  Modula-2, methods  defined  in  a
       definition module  are  technically known  as  virtual methods.    What
       exactly does this mean, and how do virtual methods differ from  another
       kind of method, known as a static method?

       Methods  defined  in  a  definition  module,  just  like  the  familiar
       procedures defined in a definition module, need to be "elaborated",  or
       "declared" in full, in the corresponding implementation module.  We saw
       an example  of this  in the  People  implementation module,  where  the
       method isMale  (for  the  Person class)  is  implemented  as  a  simple
       predicate defined on the Gender attribute  of a person, and the  method
       isSmart (for the Programmer class) is implemented as a simple predicate
       defined on one's preference for Modula-2.

       But suppose we define a further class that inherits from Programmer. To
       satisfy the boss, suppose we come up with this new class:

            DEFINITION MODULE MyKindOfProgrammers;
            FROM People IMPORT Programmer, LanguageName;

            CLASS Modula2Programmer;
              INHERIT Programmer OVERRIDE isSmart;
              compilerVendor :ARRAY [0..2] OF CHAR;
            END Modula2Programmer;

            END MyKindOfProgrammers.

       The new class Modula2Programmer now has attributes

            name, sex        (inherited via Programmer from Person)

       FST Modula-2                                                         55

            favoriteLanguage (inherited from Programmer)
            compilerVendor   (defined for itself)

       and it has methods

            isMale           (inherited via Programmer from Person)
            isSmart          (inherited from Programmer)

       So far, so  good.  But  the implementation  of Modula2Programmer  might
       wish to use a different predicate for "smartness" -- perhaps, for  this
       class of programmer, the smart ones are those that use FST Modula-2?

       We are allowed to override the implementation of an otherwise inherited
       method by stating our intention, via the OVERRIDE clause in the INHERIT
       statement  (as  above),   and  redeclaring  the   new  method  in   the
       implementation of the  subclass (in  this example,  Modula2Programmer);
       for example:

            IMPLEMENTATION MODULE MyKindOfProgrammers;
            FROM Strings IMPORT CompareStr;
            CLASS Modula2Programmer;
              PROCEDURE isSmart() :BOOLEAN;
                BEGIN
                  RETURN CompareStr(compilerVendor,"FST") = 0;
                END isSmart;
              INIT
                compilerVendor := "FST";
            END Modula2Programmer;

            END MyKindOfProgrammers.

       What about the boss?!  Well,  he can create his own programmer  classes
       if he wants to!

       What have we done?  Let's see how these classes could be used:

            MODULE x;
            FROM InOut IMPORT WriteString, WriteLn;
            FROM People IMPORT Programmer;
            FROM MyKindOfProgrammers IMPORT Modula2Programmer;
            FROM Objects IMPORT
              ALLOCATEOBJECT,DEALLOCATEOBJECT,MEMBEROBJECT;

            CLASS CProgrammer;
              INHERIT Programmer;
            END CProgrammer;

            VAR m2programmer :Modula2Programmer;
                cprogrammer  :CProgrammer;

            BEGIN
              NEW( m2programmer );
              NEW( cprogrammer );
              IF m2programmer.isSmart() THEN

       FST Modula-2                                                         56

                WriteString( "this m2 programmer is smart" ); WriteLn;
              END;
              IF cprogrammer.isSmart() THEN
                WriteString( "this C programmer is smart" ); WriteLn;
              END;
            END x.

       This program would output "this m2 programmer is smart".

       In  the  implementation  of   Modula2Programmer  we  have   effectively
       redefined the method isSmart,  and so the message  m2programmer.isSmart
       invokes this new method,  because m2programmer is of  this class.   But
       since   CProgrammer   did   not    redefine   isSmart,   the    message
       cprogrammer.isSmart invokes the method  implemented in Programmer,  and
       inherited by CProgrammer.  Makes sense, does it not?

       Now let us consider the following (similar) example:

            MODULE y;
            FROM Objects IMPORT ALLOCATEOBJECT;
            FROM InOut IMPORT WriteString, WriteLn;
            FROM People IMPORT Programmer;
            FROM MyKindOfProgrammers IMPORT Modula2Programmer;

            CLASS CProgrammer;
              INHERIT Programmer;
            END CProgrammer;

            VAR m2programmer :Modula2Programmer;
                cprogrammer  :CProgrammer;

            PROCEDURE ifIsSmart( p :Programmer; s :ARRAY OF CHAR );
              BEGIN
                IF p.isSmart() THEN
                  WriteString( s ); WriteLn;
                END;
              END ifIsSmart;

            BEGIN
              NEW( m2programmer );
              NEW( cprogrammer );
              ifIsSmart( m2programmer, "1st m2 programmer is smart" );
              ifIsSmart( cprogrammer, "1st C programmer is smart" );
              m2programmer.favoriteLanguage := "C";
              cprogrammer.favoriteLanguage := "Modula-2";
              ifIsSmart( m2programmer, "2nd m2 programmer is smart" );
              ifIsSmart( cprogrammer, "2nd C programmer is smart" );
            END y.

       In this case we would get the following result:

            1st m2 programmer is smart
            2nd m2 programmer is smart
            2nd C programmer is smart

       FST Modula-2                                                         57


       This may not  be quite  as obvious, although  it certainly  is what  we
       would have wanted: Modula2Programmer's  are smart, regardless of  their
       favorite language, if they use FST Modula-2 -- their implementation  os
       isSmart depends on their  vendor, not on  any fickle philandering  with
       other languages.

       By default,  Programmers  are  smart if  their  favourite  language  is
       Modula-2.  So, since a CProgrammer inherits the default method, all  we
       have to  do is  convince the  CProgrammer that  his favourite  language
       should change (to Modula-2) for the default method to be able to  react
       properly.

       How is it that the ifIsSmart method is invoking different procedures at
       different times, when the programmed call is, obviously, the same?  Why
       does the code in ifIsSmart not do what it appears to do, namely call  a
       method declared when we implemented the Programmer class -- after  all,
       the formal parameter p is apparently declared of the Programmer class.

       We have a feature that is known  as dynamic binding.  Since the  method
       (procedure  isSmart)  is  virtual,  the  actual  procedure  invoked  by
       p.isSmart in  ifIsSmart  is not  determined  until run  time,  when  it
       depends on  the actual  run time  type of  the object  referenced by  p
       (remember that the  actual parameter  passed to   p   can  be of  class
       Programmer or any subclass of Programmer).

       Note, incidentally, that

            MEMBER( cprogrammer, Modula2Programmer )

       would have returned FALSE, but

            MEMBER( cprogrammer, Person )

       would have returned TRUE.  We have already implied that objects have to
       be able somehow to "remember" the  actual type of object that has  been
       assigned to them, since  this can be the  class itself, or a  subclass.
       So, classes have rather more "intelligence" than the old style  records
       you have used in the past.

       The form of binding  of procedure calls that  you have become  familiar
       with in  standard Modula-2  is  known as  "static  binding" --  when  a
       compiler sees a procedure call, it normally knows at compile time which
       procedure will actually be invoked at run time (the only way it  cannot
       know  is  if  the  procedure  call  is  via  a  "procedure  variable").
       Hopefully, you can see that this is more "efficient" than delaying  the
       decision till run time.  Hopefully, you can also see that being able to
       delay  the  decision  opens  the  door  to  a  whole  new  approach  to
       programming.

       For those familiar with other OOP languages we point out that, in  some
       applications of OOP techniques, the programmer  may be able to  foresee
       that the ability to override a method may never be needed, and that the
       inefficiency of delaying a decision until  run time that really can  be

       FST Modula-2                                                         58

       made at compile  time is simply  unwarranted.  So,  some OOP  languages
       (but not FST) allow  one to declare methods  (at the definition  module
       level) as either VIRTUAL (that is, to allow overriding) or STATIC  (not
       allow it).  The distinction is  usually drawn by using a reserved  word
       like VIRTUAL where needed,  and omitting it  where "static" is  implied
       (just as  when  we  omit VAR  to  get  parameters passed  by  value  by
       default).

       For simplicity, FST Modula-2 has the following rules:

         methods defined in a definition module are all virtual

         methods  defined  in a  definition  module  may be  overridden  in  a
         subclass by  methods that  must, however,  be of  the same  procedure
         type as the original method (some other languages relax this rule)

         further  local methods,  declared  in an  implementation  or  program
         module, are static and cannot be overriden in descendant classes.

       This means that the following code is not allowed.

            MODULE SexualHarassment;

            TYPE
             Gender = (unknown, male, female);

            CLASS Person;    (* a class implementation *)
             sex :Gender;

             PROCEDURE isEligible () :BOOLEAN;  (* a static method *)
               BEGIN
                 RETURN sex = male;
               END isEligible;

             INIT
               sex := unknown;
             END Person;

            CLASS Woman;     (* a class implementation *)
             INHERIT Person; (* women are persons too *)

             PROCEDURE isEligible () :BOOLEAN
             (* illegal - cannot override a static method *);
               BEGIN
                 RETURN sex = female;
               END isEligible;

             INIT
               sex := female;
             END Woman;

            END SexualHarassment (* and not before time either *).

       Chauvinist system.  To be eligible one has to be male.  Oh well!

       FST Modula-2                                                         59

       14.14 Data Structures with Classes

       We have  already hinted  on several  occasions that,  in FST  Modula-2,
       classes are really clever pointers to clever records.  This means  that
       setting up dynamic lists,  trees and the  like is very  easy.  It  also
       means that  we  at  last  have the  possibility  of  setting  up  truly
       extensible and "generic"  base classes  for nodes  in such  structures,
       from which special purpose variations may easily be crafted without the
       need for editing or recompiling the base classes.

       To illustrate this, let us consider  the workhorse data structure,  the
       stack.  A stack is built of a sequence of nodes, and an  implementation
       of a stack ADT entails operations to push and pop nodes at least,  plus
       a few other expedient access procedures.   Consider the following  very
       rudimentary definition:

            DEFINITION MODULE Stacks;

             (* Base types for NODES to be stored in STACKS *)

               CLASS NODE;
                 (* We hide the structure of a NODE in the implementation,
                    as the user has no need to access that information *)
               END NODE;

               CLASS STACK;

                 PROCEDURE Push ( N :NODE );

                 PROCEDURE Pop ( VAR N :NODE );

                 PROCEDURE IsEmpty () : BOOLEAN;

                 (*
                 DESTROY:
                   When this stack is DISPOSEd, all the nodes in the
                   stack are DISPOSEd too.
                 *)

               END STACK;

            END Stacks.

       Here the NODE class seems to be utterly "useless", for it seems to have
       no attributes (either declared, or made accessible through methods)  in
       which we could store any "real" data.  Hopefully the reader can foresee
       that we shall be able  to extend this class  in client programs to  add
       such data  attributes.   The STACK  class,  similarly, has  no  visible
       attributes, though it  does have methods  for pushing  and popping  our
       base class nodes, and for testing for an empty stack.

       Implementation of this pair of classes is straightforward:

            IMPLEMENTATION MODULE Stacks;

       FST Modula-2                                                         60


               FROM Objects IMPORT ALLOCATEOBJECT, DEALLOCATEOBJECT;

               CLASS NODE;   (* IMPLEMENTATION *)

                 Next :NODE; (* effectively a pointer to next node
                                in the stack *)

                 INIT        (* Next = NIL means that we are not
                                in any list *)
                   Next := NIL;

               END NODE;

               CLASS STACK;  (* IMPLEMENTATION *)

                 Top :NODE;   (* Hidden - Top node on the stack *)

                 PROCEDURE Push ( N :NODE );
                   BEGIN
                     N.Next := Top;
                     Top := N;
                   END Push;

                 PROCEDURE Pop ( VAR N :NODE );
                   BEGIN
                     N := Top;
                     Top := N.Next;
                     N.Next := NIL (* N is no longer in any stack *)
                   END Pop;

                 PROCEDURE IsEmpty () : BOOLEAN;
                   BEGIN
                     RETURN Top = NIL
                   END IsEmpty;

                 PROCEDURE DestroyStack;
                 (* DestroyStack is coded as a separate static method
                    instead of doing the work directly in DESTROY,
                    because it requires a work node *)
                   VAR
                     N :NODE;
                   BEGIN
                     WHILE NOT IsEmpty() DO
                       Pop( N );
                       DISPOSE( N );
                     END;
                   END DestroyStack;

                 INIT
                   Top := NIL   (* initialize all our attributes *);

                 DESTROY
                   DestroyStack;

       FST Modula-2                                                         61


               END STACK;

            END Stacks.

       The code should have a familiar feel, and yet look slightly strange  at
       first -- the familiar dereferencing operators (^) have all gone away.

       When we create  a stack, say  S, it is  as though we  create a  "header
       node" with a Top field, and set it to  NIL.  But rather than having  to
       code a procedure like

            PROCEDURE IsEmpty ( S :STACKTYPE ) :BOOLEAN;
            BEGIN
                RETURN S^.Top = NIL
            END IsEmpty;

       we simply have to code a corresponding method

            PROCEDURE IsEmpty () :BOOLEAN;
            BEGIN
                RETURN Top = NIL
            END IsEmpty;

       Remember that the method "knows" about the implicit parameter -- recall
       that we invoke the method with a "message" like S.IsEmpty() rather than
       calling the procedure IsEmpty(S).

       The only subtle point to which we should draw attention is that,  since
       a NODE object includes a (hidden) link to other such objects, we  shall
       never be able to store a given object in more than one stack at a time.
       This means that we  must maintain the integrity  of the Next  "pointer"
       for each node rather systematically.

       A very  simple  program that  uses  this  base class  by  extending  it
       locally, so as to provide nodes  that have an INTEGER item field  might
       be as follows:

            MODULE UseStack;

              FROM Stacks  IMPORT NODE, STACK;
              FROM Objects IMPORT ALLOCATEOBJECT;
              FROM InOut   IMPORT WriteInt;

              CLASS INTNODE;
                INHERIT NODE;
                Item :INTEGER;
              END INTNODE;

              VAR
                S  :STACK;
                I  :INTNODE;
                J  :CARDINAL;

       FST Modula-2                                                         62

              BEGIN
                NEW( S )                ( * instantiate the stack * );
                FOR J := 1 TO 5 DO
                  NEW( I ); I.Item := J ( * instantiate an integer node * );
                  S.Push( I )           ( * and push it onto the stack * );
                END;
                FOR J := 1 TO 5 DO
                  S.Pop( I )            ( * pop an integer node * );
                  WriteInt( I.Item, 0 ) ( * and display its value * );
                END;
            END UseStack.

       For a rather larger example of the use of classes as they apply to data
       structures, please  look at  the modules  Lists, MyLists  and  Uselists
       supplied with the implementation.


       14.15 The technical details

       When we declare a CLASS, the compiler generates a class template.   The
       class template  is a  structure  (the header  of  which is  defined  in
       Objects.DEF and shown below)  that contains a  reference to the  parent
       class, the size of objects of this class, and a pointer to each  method
       in this class.

            ClassHeader = RECORD
              parent      :Class;
              size        :CARDINAL;
              filler      :CARDINAL;
              InitProc    :PROCEDURE( ADDRESS );
              DestroyProc :PROCEDURE( ADDRESS );
              ... other virtual methods ...
            END;

       When an  object  is  created, the  ALLOCATEOBJECT  procedure  allocates
       enough space to hold the data in the object's structure plus a  pointer
       to the object's class.  So, as you can see, regardless of the  declared
       type of the object handle that  refers to a particular object, we  can,
       by looking at the object itself, determine what its real type is.

       As you might have guessed by now, we invoke a method by picking up  its
       address from the class template referenced by the object itself.

       There is only one  exception to this.   As was stated earlier,  methods
       declared in implementation modules are static.  These methods cannot be
       redefined and, therefore, are not placed in the class template.

       In summary:

         A virtual method may  be redefined and its invocation is  dynamically
         resolved at run time.

         A static  method cannot be redefined  and its invocation is  resolved
         at compile time.

       FST Modula-2                                                         63

         All methods defined in a definition module are virtual.  All  methods
         first declared in a program or implementation module are static.

       It may also help to give  a more insightful explanation of the  methods
       INIT and DESTROY.

       We have  seen  that  the  methods  defined  in  INIT  and  DESTROY  are
       automatically invoked when an object is created or destroyed.

       When the  compiler sees  a NEW(objectHandle),  it generates  code  that
       invokes the  procedure  ALLOCATEOBJECT,  which must  be  known  in  the
       current environment -- you must either  import this procedure from  the
       standard module Objects or write your own.

       ALLOCATEOBJECT takes two parameters:  a VAR object  handle and a  class
       identifier (pointer to the class template).

       First, ALLOCATEOBJECT calls Storage.ALLOCATE to allocate memory for the
       object and stores the address of the class template in the first double
       word of the object just allocated.

       Next, and starting with the base class in the hierarchy, ALLOCATEOBJECT
       invokes all the INIT methods that were  defined along the way.  In  the
       previous  example,  the  call  NEW(m2programmer)  caused   person.INIT,
       Programmer.INIT and  Modula2Programmer.INIT  to  be  invoked,  in  that
       sequence.

       For  DISPOSE(objectHandle),   the   compiler  generates   a   call   to
       DEALLOCATEOBJECT, also defined in Objects.

       DEALLOCATEOBJECT works  in the  reverse order  of ALLOCATEOBJECT.    It
       invokes all  the  DESTROY  methods  defined  in  the  class  hierarchy,
       starting with  the  class of  the  current  object.   It  then  invokes
       Storage.DEALLOCATE to free the object's memory.


       14.16 On your own...

       This concludes our  introduction to object  oriented programming  using
       FST Modula-2.

       FST Modula-2                                                         64


       15. License terms

       You are given a non-exclusive license to use this software.
