Modula-2: the structured form of Pascal

Modula-2 was developed by Niklaus Wirth the inventor of Pascal. Modula-2 was designed for constructing operating systems. The first OS for which it was used, was Lilith. Probably this was the last one too, since most of the "middle of the road programmers" didn't or couldn't switch to the new language. "C is for Chaos". And not only as an acronym: C is a write-only language.
Modula-2 is superstructured. It is case sensitive. There are only few internal commands. The rest is imported from libraries, which are simple to make yourself with the language itself.

Modula-2 is based on code-hiding: as a programmer you produce a ".DEF" file in which you declare how each procedure works, plus a precompiled ".M2O" file which can be linked in by the user to complete the total code. You can keep your sources to yourself.
The biggest drawback of Modula-2 is it's lack of a GOTO statement! There you go, as a seasoned C++ progammer. No GOTO! How on earth are you now expected to leave tricky bits of code?

Modula-2 is equipped with a series of statements which give enough power such that you will never, ever, need a GoTo statement. Below, I have put a small piece of code. It is part of the soap hardware (for more information: see the mainpage), an 11 channel 12 bit AD converter project. Originally written in assembly language, and now ported to Modula-2.

A sample program: SOAP, a mouse driven program.

This source produces an executable which is completely mouse-driven. The keyboard is superfluous as long as this program is running! Start the program SOAP02 (it is included with the full source in the file SOAP02.ZIP) and try to return to the operating system. It can be done but you must use your imagination.
Below is part of the source. I removed all the irrelevant pieces of code. The full source is part of the ZIP file. Just download it and get access to everything needed to compile it.

SOAP: the source of beauty.

MODULE Soap02;

FROM    Display         IMPORT  SetCursorPosition;
FROM    InOut           IMPORT  Write, WriteCard, WriteLn, WriteLine, WriteHex;
FROM    Mouse           IMPORT  ConfineMouse, GetMouseStatus, HideMouse, InitMouse,
                                MouseRecord, SetMouseCursor, ShowMouse;
FROM    SYSTEM          IMPORT  ADDRESS, ASSEMBLER;
FROM    System          IMPORT  GetArg, Terminate;
FROM    Timer           IMPORT  LongWait;
FROM    VgaLib3         IMPORT  WinData, COLOUR, SetVGA, ChrWid, Center, SetColour, MakeBox,
                                EraseBox, FillBox, PlotChar, WriteNumber, DrawH, PlotText;


TYPE    LogicState          = (Low, High);


CONST   AdcResolution       =  12;      (*  nr of bits of TLC 2543 ADC      *)
        AdcChannels         =  11;      (*  nr of channels in this ADC      *)
        Tab                 = 11C;


VAR     MainScreen, PlotWin, BotLin, TopWin, MousWin     : WinData;

        Inimini                             : MouseRecord;

        Number, StopCondition, OldMode,
        LptPort, NextChannel,
        Value, i, j                         : CARDINAL;
        BipolarFormat                       : BOOLEAN;


PROCEDURE InitWins;

BEGIN
(*  MainScreen is a dummy for the all around framework.     *)

    MainScreen.TopX   :=   0;       MainScreen.TopY   :=   0;
    MainScreen.Width  := 640;       MainScreen.Height := 480;
    MainScreen.CurX   :=   0;       MainScreen.CurY   :=   0;
    MainScreen.DeltaX :=   0;       MainScreen.DeltaY :=   0;
    MainScreen.Indent :=   5;
    MainScreen.TexCol := GREEN;     MainScreen.BoxCol := YELLOW;
    MainScreen.BckCol := black;     MainScreen.MnuCol :=    red;

(*  TopWin describes the upper status bar for action keys.  *)

    TopWin.TopX     :=  10;         TopWin.TopY     :=  10;
    TopWin.Width    := 550;         TopWin.Height   :=  60;
    TopWin.CurX     :=   0;         TopWin.CurY     :=   0;
    TopWin.DeltaX   :=   0;         TopWin.DeltaY   :=   0;
    TopWin.Indent   :=   4;
    TopWin.TexCol   := RED;         TopWin.BoxCol   := green;
    TopWin.BckCol   := pink;        TopWin.MnuCol   :=   RED;

(*  MousWin describes the window for the mouse coordinates  *)

    MousWin.TopX    := 580;         MousWin.TopY    :=  10;
    MousWin.Width   :=  50;         MousWin.Height  :=  60;
    MousWin.CurX    :=   0;         MousWin.CurY    :=   0;
    MousWin.DeltaX  :=   0;         MousWin.DeltaY  :=   0;
    MousWin.Indent  :=   4;
    MousWin.TexCol  :=  white;      MousWin.BoxCol  :=  white;
    MousWin.BckCol  :=   blue;      MousWin.MnuCol  := YELLOW;

(*  PlotWin describes the uppermost window for the parameters.   *)

    PlotWin.TopX    :=  10;         PlotWin.TopY    := 155;
    PlotWin.Width   := 620;         PlotWin.Height  := 300;
    PlotWin.CurX    :=   0;         PlotWin.CurY    :=   0;
    PlotWin.DeltaX  :=   0;         PlotWin.DeltaY  :=   0;
    PlotWin.Indent  :=   4;
    PlotWin.TexCol  := YELLOW;      PlotWin.BoxCol  := cyan;
    PlotWin.BckCol  :=  black;      PlotWin.MnuCol  :=  red;

(*  BotLin describes the Copyright notice.                  *)

    BotLin.TopX     :=   5;         BotLin.TopY     := 460;
    BotLin.Width    := 620;         BotLin.Height   :=  20;
    BotLin.CurX     :=   0;         BotLin.CurY     :=   0;
    BotLin.DeltaX   :=   0;         BotLin.DeltaY   :=   0;
    BotLin.Indent   :=   4;
    BotLin.TexCol   := brown;       BotLin.BoxCol   := green;
    BotLin.BckCol   := black;       BotLin.MnuCol   := green;

END InitWins;


PROCEDURE SetUpScreen;

BEGIN
    OldMode := SetVGA (12H);
    InitMouse (Inimini);
    InitWins;
        MakeBox (MainScreen);
        MakeBox (PlotWin);
        MakeBox (MousWin);
    TopWin.CurX := 10;
    TopWin.CurY :=  0;
    Center (TopWin, "SOAP_version_1.0 Load Save Run Cls Quit");
    BotLin.CurX := 10;
    BotLin.CurY :=  0;
    Center (BotLin, "Copyleft_2001: Jan_Verhoeven jverhoeven@bigfoot.com")
END SetUpScreen;

PROCEDURE SetUpMouse;

BEGIN
    ShowMouse;
    IF Inimini.Buttons = 2 THEN
        StopCondition := 3
    ELSE
        StopCondition := 7
    END;
    SetMouseCursor (30, 30)
END SetUpMouse;


PROCEDURE ShowMouseXY;       (*  Show current mouse data on screen.  *)

BEGIN
    SetColour (MousWin.TexCol, MousWin.BckCol);
    MousWin.CurX := MousWin.Indent;
    MousWin.CurY := 4;
    WriteNumber (MousWin, Inimini.MouseX, 4);
    MousWin.CurX := MousWin.Indent;
    MousWin.CurY := 23;
    WriteNumber (MousWin, Inimini.MouseY, 4);
    MousWin.CurX := MousWin.Indent;
    MousWin.CurY := 42;
    WriteNumber (MousWin, Inimini.Status, 4)
END ShowMouseXY;


PROCEDURE UserMessage (n  : CARDINAL);

BEGIN
    WriteLn;
    CASE n OF
     0 :    WriteString ("Thank you for using this software. ");
            WriteLine ("Remember that this is GNU GPL free software");
            WriteString ("which comes with NO GUARANTEE but full sources. ");
	    WriteLine ("You use this program at your");
            WriteLine ("own risk. Please study the GNU GPL conditions.");
            WriteLn;
            WriteLine ("Copyleft 2000, jverhoeven@bigfoot.com");
            |
     1 :    WriteLine ("The syntax for using this software is as follows:");
            WriteLn;        Write (Tab);
            WriteLine ("SOAP LptPort SampleRate <ADC channel set>");
            WriteLn;
            WriteString ("in which :      LptPort is the number of the LPT port ");
	    WriteLine ("(1, 2 or 3)");
            Write (Tab);    Write (Tab);
            WriteLine ("SampleRate is the number of samples per second");
            Write (Tab);    Write (Tab);
            WriteString ("<ADC channel set> is an optional list of channels ");
	    WriteLine ("that are measured");
            WriteLn;
            WriteLine ("If no ADC channel set is specified, all channels are sampled.")
    END
END UserMessage;
   

In-line assembly, the easy way.

PROCEDURE ChipSelect (state  : LogicState);         
                                      (*  Control the TLC 2543 ChipSelect line  *)

BEGIN
    IF state = Low THEN
        ASM
            MOV  DX, LptPort
            IN   AL, DX
            OR   AL, 1
            OUT  DX, AL
        END
    ELSE
        ASM
            MOV  DX, LptPort
            IN   AL, DX
            AND  AL, 0FEH
            OUT  DX, AL
        END
    END
END ChipSelect;
   
See how easy it was to include a piece of inline assembly? If the ChipSelect line must be "1" or HIGH, another piece of code is executed as when the line must be LOW or "0".
There is also almost no special code necessary to start some inline assembly. In the manuals, the maker of this compiler has to discourage people from including too much inline assembly with his remark: "Remember, this is a compiler, not an assembler."

Back to the source....

PROCEDURE ShiftClock (state  : LogicState);         
                                     (*  Control the TLC 2543 Shift Clock input *)

BEGIN
    IF state = Low THEN
        ASM
            MOV  DX, LptPort
            IN   AL, DX
            OR   AL, 2
            OUT  DX, AL
        END
    ELSE
        ASM
            MOV  DX, LptPort
            IN   AL, DX
            AND  AL, 0FDH
            OUT  DX, AL
        END
    END
END ShiftClock;


PROCEDURE SerialDataInput (state  : LogicState);     

                               (*  Control the TLC 2543 Serial Data Input      *)

BEGIN
    IF state = Low THEN
        ASM
            MOV  DX, LptPort
            IN   AL, DX
            OR   AL, 4          (*  Set bit 2 of the LPT port       *)
            OUT  DX, AL
        END
    ELSE
        ASM
            MOV  DX, LptPort
            IN   AL, DX
            AND  AL, 0FBH       (*  Clear bit 2 of the LPT port     *)
            OUT  DX, AL
        END
    END
END SerialDataInput;


PROCEDURE ReadEOC () : LogicState;          

                           (*  Determine state of the End Of Conversion output     *)

VAR     Result      : CARDINAL;

BEGIN
    ASM
        MOV  DX, LptPort
        INC  DX
        IN   AL, DX
        AND  AL, 040H
        MOV  Result, AL
    END;
    IF Result = 0 THEN
        RETURN Low
    ELSE
        RETURN High
    END
END ReadEOC;
   

The standard way of returning parameters from Inline assembly

PROCEDURE ReadDataOutput (VAR word  : CARDINAL);
                          (*  Read TLC 2543 Serial Data Output and store in word  *)

VAR     value       : CARDINAL;

BEGIN
    value := word;
    ASM
        MOV  DX, LptPort
        INC  DX                 (*  Point to inputchannel   *)
        IN   AL, DX
        SHL  AL, 1              (*  bit 7 -> CF             *)
        CMC                     (*  invert it               *)
        MOV  AX, value          (*  get current value       *)
        RCL  AX, 1              (*  shift in next bit       *)
        MOV  value, AX          (*  and store in memory     *)
    END;
    word := value
END ReadDataOutput;


PROCEDURE ReadWord (NextChannel : CARDINAL) : CARDINAL;

(*  Read in result of previous conversion, while preparing the TLC 2543 ADC 
    for the next conversion. NextChannel contains the next ADC channel number.
    *)

VAR     LastValue, i, bit   : CARDINAL;

BEGIN
    NextChannel := NextChannel * 4096;
    IF BipolarFormat THEN INC (NextChannel, 256) END;
    LastValue := 0;
    ChipSelect (Low);
    FOR i := 1 TO AdcResolution DO
        bit := 16 - i;
        ReadDataOutput (LastValue);
        IF bit IN BITSET (NextChannel) THEN
            SerialDataInput (High)
        ELSE
            SerialDataInput (Low)
        END;
        ShiftClock (High);
        ShiftClock (Low)
    END;
    ChipSelect (High);
    RETURN LastValue
END ReadWord;
   

Modula 2 is almost self documenting.

As you can see here: all previously defined procedures and modules come together and fit the puzzle. The language is virtually "self documenting". You can pick (just about) any name and the case sensitivity of the language gives the programmer enormous flexibility in assigning names to functions, variables and procedures. All names can be made meaningful from the very first instant.

Comments remain useful, but often the name is already enough explanation for things to come.

Back to the source....

PROCEDURE OnScreen;

VAR     i, j, value         : CARDINAL;

BEGIN
    value := ReadWord (0);
    FOR i := 0 TO 10 DO
        value := ReadWord (i+1);
        SetCursorPosition (i+4, 10);
        WriteCard (value, 8);
        WriteHex (value, 12)
    END
END OnScreen;


PROCEDURE Initialize;       (*  Set up defaults, read command line      *)

VAR     Option          : ARRAY [0..7] OF CHAR;
        count, i        : CARDINAL;
        ch              : CHAR;

BEGIN
    GetArg (Option, count);
    IF count = 0 THEN       (*  No commandline arguments    *)
        UserMessage (1);
        Terminate (1)
    END;
    IF CAP (Option [0]) = 'L' THEN
        ch := Option [3]
    ELSE
        ch := Option [0]
    END;
    i := ORD (ch) - ORD ('1');
    ASM
        MOV   AX, 0
        MOV   ES, AX
        MOV   BX, 0408H
        MOV   AX, i
        SHL   AX, 1
        ADD   BX, AX
        MOV   AX, ES:[BX]
        MOV   i, AX
    END;
    LptPort := i;
    ChipSelect (High);
    ShiftClock (Low);

    SetUpScreen;
    SetUpMouse;
    BipolarFormat := FALSE
END Initialize;


BEGIN
    Initialize;
    REPEAT
        GetMouseStatus (Inimini);
        ShowMouseXY;
    UNTIL Inimini.Status = StopCondition;

    ChipSelect (High);
    i := SetVGA (OldMode);
    UserMessage (0)
END Soap02.
   

Page created ??? and