Making the Move to Modula-2

Modula-2's modular structure is ideal for team programming projects and for creating efficient, reusable code.


September 01, 1990
URL:http://drdobbs.com/tools/making-the-move-to-modula-2/184408407

Figure 2

SEP90: MAKING THE MOVE TO MODULA-2

MAKING THE MOVE TO MODULA-2

Modular structure is important for multiprogrammer projects

J.V. Auping and J.C. Johnston

Judy Auping and Chris Johnston work in the Microgravity Materials Science Laboratory at NASA's Lewis Research Center in Cleveland. They both hold Ph.D.s in chemistry from Cleveland State University. Chris Johnston's book, The Microcomputer Builder's Bible, was published by Tab Books in 1983. They can be reached at 216-433-5016 and 216-433-5029, respectively.


Three years ago, we had occasion to upgrade a group of small scientific computers used for laboratory data acquisition. We chose to standardize all of our new software efforts and use Modula-2. In this article, we'll talk about why we chose Modula-2 and about the benefits and drawbacks of the language.

As support programmers for a materials research laboratory, we are involved in writing application programs that perform control functions and acquire data from special-purpose experimental furnaces. There are about a dozen such furnaces for controlled heating and cooling of metals and alloys and for crystal growth activities. Each furnace had been initially equipped with a data acquisition and control system made up of a small desktop computer that was programmable in Basic and an IEEE-488-based data acquisition unit. As our researchers came up with more complex experiments, we were limited by the speed and memory capacity (32K words of user space) of the desktop computers.

In addition, as the researchers became familiar with the capabilities of the PC systems appearing in their offices, they grew dissatisfied with the small display and slow tape drives of the data acquisition computers. The decision was made to replace the old computers with faster, larger, PC-type systems. We had been quite satisfied with the capabilities of the data acquisition units, and also had invested significant amounts of time in the wiring of thermocouples and other transducers. We were determined to keep them with whatever new systems we developed. This put some constraints on our choice of hardware.

After a survey of the available computers, we settled on an 80286-based AT-compatible system with a 40-Mbyte hard disk and EGA display. We also chose an IEEE-488 interface card that promised code to support most of the popular programming languages. Since we had been dissatisfied with the cryptic nature of even our own most carefully written Basic code, we were pleased to have the chance to move to a more satisfactory programming language. This was an important decision for us, because we wanted to standardize on one language throughout the lab. As the different furnaces have many functions in common, we wanted to be able to share code.

Looking at Languages

The first language we considered was Pascal, as one of us had had considerable experience with it prior to this project. After trying to apply Pascal to our experiments, we decided we liked the strong typing and structured design of the language, but were concerned that it would be unable to support a joint programming effort. We had been intrigued by literature{1,2 that described Modula-2 as a language designed for team programming. Its similarity to Pascal was convenient, and it seemed well-suited to joint projects that required low-level access to the computer hardware. So, we decided to test a Modula-2 development system.

We were particularly interested in and wanted to evaluate a number of features described in the articles on Modula-2:

A number of things looked as if they would be real (if minor) annoyances:

After installing the Modula-2 development system and writing a few short test programs, we were sufficiently encouraged to embark on a fairly major "learning project." We looked around for something that would be a good multiprogrammer test project and that would be useful to us later if we decided to standardize on Modula-2. At that time, nearly all of our experiments were already running on the smaller computers, generating data that required plotting. Our researchers had also begun clamoring for the ability to view their data graphically while an experiment was in progress. To evaluate Modula-2's ability to solve our problems we decided to implement a library of screen and plotter graphing routines for our data acquisition and control programs.

Developing Modula-2 Libraries

The first step in developing our library was to determine exactly what capabilities it would provide. We were pleased with the plotting functions available in the Basic ROMs in our small computers, and we decided to include many of these functions but improve on the calling syntax. This gave us a base set of "high-level" procedures that would have to be implemented. These would be the only procedures invoked directly by an applications programmer using the library. We defined a number of other functions that would be needed either as additions to the base set or as lower-level functions required to implement the base calls.

Once we had an idea of what we had to write, we needed to break the project into two more or less equal pieces. Based on our examination of the procedures we had already identified, the project seemed to divide naturally between the low-level hardware manipulation functions and the higher-level graphing calls. The procedure for drawing an axis doesn't need to know about how the lines are drawn or that drawing lines on the display and the plotter are handled differently. On the other hand, the line drawing procedure doesn't need to know what is being drawn. Table 1 lists the low-level hardware manipulation and high-level plotting functions. Although it may look like an unequal division of labor, most of the low-level function calls contain separate code for each plotting device included in the system; so the low-level section required more lines of code.

Table 1: Library functions

  Low-level Hardware Manipulation Functions

  Determine what graphics devices are available
  Move the active position to the specified device coordinates
  Draw from the active position to the specified device coordinates
  Draw a label string
  Clear the video screen and set to text, graphics, or menu (no
  cursor) mode

  High-level Plotting Functions

  Initialize the graphics functions
  Choose a plotting device for subsequent output
  Set plotting boundaries
  Set boundaries for a scaled area
  Set the scale for the scaled area
  Set the type of plotting units
  Set the size of characters for subsequent labels
  Set the angle for subsequent labels
  Set the line type for subsequent draw commands
  Set the pen color
  Set the background color
  Set the label orientation relative to the active position
  Set the number of digits to the right of the decimal point
  Return the ratio of the physical dimensions of the plotting area
  of the current device
  Set the character font to be used in subsequent labels
  Draw a line from the active position to the specified coordinates
  Move to the specified coordinates
  Do an incremental draw from the active position
  Do an incremental move from the active position
  Draw an X-axis with tic marks
  Draw a Y-axis with tic marks
  Draw a set of X-Y axes with tic marks
  Draw an X-Y grid
  Draw a label at the active position according to the current
  character size, label orientation, and label angle settings
  Draw a set of X-Y axes with tic marks, labelled according to the
  current character size and fixed decimal settings
  Draw an X-Y grid, labelled according to the current character
  size and fixed decimal settings
  Draw a box around the current plotting area
  Draw a symbol of specified size and type at the active position
  Return the coordinates of the active position
  Clear the screen and set the mode

In Modula-2, all modules except for the main program module have two parts: A definition module that formally defines what procedures and variables are available, and an implementation module where the executable code to perform those functions resides. The definition module for the high-level plotting functions, ModPlot, is shown in Listing One, page 76, and the module for the low-level functions GDriver is shown in Listing Two, page 76. We also created the module DataDefs (Listing Three, page 76) to hold all of the global types. By defining color types, device types, and so forth, we are able to use statements such as SetBackgroundColor(Blue) or SetPlotDevice (EGA) rather than a cryptic numerical code. The price we pay for this increased program readability is that the relevant types must be explicitly imported from DataDefs into every other module using those types. Note that DataDefs is an example of a Modula-2 definition module with an empty implementation module. It allows us to define a number of types and variables to be imported whenever necessary but contains no executable code; its only function is to define the variables.

In our case, determining which of us would do what part of the project was easy. The division into high-level (user-related) and low-level (hardware-related) functions matched our interests fairly well. But we still had to decide how to pass information from one part to the other and how to handle range checking and error reporting. Since we already had the DataDefs module for defining global types, we decided to use it to also hold a set of global variables that would contain the current settings for the graphics parameters. This would reduce the number of formal arguments needed to pass information. We decided to do all of the checking for the validity of data in the high-level procedures. The procedures in GDriver trust that they are being passed legal pixel coordinates by the code that calls them.

Part of the idea behind Modula-2 is that it allows programmers to define an interface between modules that is stable and isolates one module from another. In theory you should think deep thoughts about the interface (the definition module), define it, and never change it again. In our experience, reality was only a little different. Both the high-level ModPlot definition module and the low-level GDriver definition module were remarkably stable once we had thought about them for a while. GDriver did require one addition, the CleanUp procedure, unforeseen in the original design. (The cleanup procedure was needed because of the way one of the compiler-supplied library modules managed the interrupt system. Another compiler might handle things differently, making the additional procedure unnecessary.) We made a few minor changes to the types of the arguments in some of the ModPlot procedure calls. The DataDefs module, however, an important behind-the-scenes part of the interface, was a lot more fluid.

The actual process of implementing our library was straight-forward. We spent two weeks defining and partitioning the functions. We then went our separate ways to do the coding and some independent testing of the sections. Obviously, as in any hierarchically divided project, it was more satisfying to test the low-level modules. Testing of the high-level ModPlot functions was limited to producing textual output such as "Call to DrawAbs made with the following arguments ...", whereas the testing of the low-level procedures generated pretty pictures right from the start. On the other hand, when the low-level modules malfunctioned they were harder to debug, as the errors usually were not obvious and sometimes were catastrophic. After about a month of separate coding and testing, we were able to link the two sections together in a test program. We spent a relatively short time using test programs to debug the library. The whole project took about two months (four man months) from start to finish.

Listing Four, page 77, and Figure 1 show the source code and output of a program, Example, which demonstrates the use of the ModPlot library. Note that Example 1 imports types from DataDefs that do not explicitly appear in variable declarations, but do have instances appearing in the code. For example, SizeType must be imported because Small is used in the statement SetCharSize(Small).

Team Programming and Modula-2

Of course, the big question is, how did the use of Modula-2 affect the process of writing a team-programming project? Our feeling is that the modular nature of the language streamlined the process of writing and testing each function. Granted, this could be done with any "good" structured language, but the formal, definition module/implementation module structure made it particularly easy to isolate and group similar procedures.

Comparison with our past experience shows that the strict typing of Pascal or Modula-2 eliminates the source of a lot of errors that creep into Basic or Fortran programs. In comparing Pascal to Modula-2, it is more difficult to see a great difference. The tendency (in well-written programs, at least) for Modula-2 programs to be broken into many small parts makes the isolation and elimination of errors easier than in Pascal and makes Modula-2 code more reliable from the outset than Pascal or C code. We have, of course, found occasional bugs in our library during the past year and a half. Almost all of these, however, have been due either to errors in logic or to timing problems caused by moving to machines with different clock speeds.

The reusability of modules is an important part of the rationale behind Modula-2. Once you write and debug a module you can put it in your library and use it whenever you need that function again. A problem arises in modules that import information from other modules. GDriver imports a module called HdwDefs. That means that GDriver can't be used in another program unless HdwDefs is available too. We have found that the most easily reusable modules are often the lowest-level ones that perform one simple, clearly focused function. Reusability of more complicated modules must be planned for in the original design of the code because you need to minimize the number of dependencies.

On the other hand, GDriver is a general-purpose drawing and labeling module available for any program needing it. This approach is preferable to writing the same functions again the next time they are needed. In a broader sense, this was the purpose of the entire plotting package. It provides a simple interface for a programmer who needs to add some simple graphing functions to a program. As a byproduct of its creation we now have a number of modules that we can use anytime.

The issue of transparent access to low-level facilities was also important to us. Some of the functions that we require in our experiments involve the use of boards that plug into the AT bus. Many of these require special software to access their registers and onboard memory. Modula-2 has enabled us to do that quite successfully. Some of these devices use interface code that is wholly or partially written in assembly language but is called from a Modula program as if it were any other module. Modula-2 can access individual memory locations and I/O ports, allowing the programmer to do much of this manipulation without resorting to assembler. It is, however, a good idea to isolate any such extremely machine-dependent code in its own module.

Error handling has been and continues to be more of a problem. If an external error occurs deep in a nested set of procedures, there is no easy way of invoking some error-handling procedure unless an error flag is tested each step of the way back. For example, if the plotter goes offline during a plot (someone inadvertently turns it off or the paper-load lever is moved), the transmission code deep in the low-level routines will eventually time out, generating a serial error. The error is posted in a variable in DataDefs along with a string explaining the problem. There is no way to guarantee that the calling program will notice this condition unless the error flag is checked after every call to GDriver. This means that every call to DrawAbs, MoveAbs, or DrawString in GDriver has to be checked, and the rest of the calling routine short-circuited, returning an error indication to the external calling program. This issue has not been resolved to our satisfaction. Our working solution has been to have the low-level code that discovers the error post a message to a reserved area on the display screen explaining the problem and giving the user a chance to fix it.

The Upside and Downside of Modula-2

One unanticipated benefit of Modula-2 has been that it allows us to maintain our own coding styles and habits and a sense of ownership of our own code without impeding the collaboration necessary to make a project like this work. (As you may have gathered, we have not progressed to the point of "egoless" programming. We are, at best, pursuing "ego-reduced" programming.) For example, one of us likes the indented structure:

  IF SomeCondition THEN
       DoThis;
       ThenThat;
   ELSE
       DoSomethingElse;
   END (*if*);

while the other prefers:

  IF SomeCondition
       THEN
           DoThis;
           ThenThat;
       ELSE DoSomethingElse;
   END (*if*);

The fact that we can each write and maintain our own code in separate modules means that neither has to put up with the other's unspeakably ugly IF-THEN style.

We expected that the language's case sensitivity would turn out to be an annoyance. Instead, we have found that once you are used to the rules it isn't a burden at all, especially if you use a syntax-assisted editor. In fact, we have both grown to like the fact that reserved words are all in caps. Using normal capitalization for variable and procedure names allows you to quickly pick them out from the reserved words in the code. Case sensitivity forces you to clean up the variable names so they all match exactly. In Pascal, case insensitivity allows you to put off cleaning up the code to make it prettier. Modula-2's case sensitivity does have a drawback in that it makes some new errors possible. It is legal to declare a local variable with the same name as a global variable. No changes can be made to the global variable inside the procedure where the local variable is declared, protecting the global from unforeseen side effects. However, if you miscapitalize the declaration of the local you can inadvertently change the global. This situation actually occurred in some code a student wrote for us, and it took quite a while for us to help him locate the problem.

A real annoyance with Modula-2 is that it has no equivalent for Pascal's get and put statements, which allow a program to automatically handle I/O on complex data structures such as records. In order to write a record variable in Modula-2, a program has to calculate the size of the record and then write that number of bytes. Presumably, this is in keeping with the philosophy that all I/O in Modula-2 would be through a procedure specific to the data type involved. It would be nice if there were a more automatic way to do this. The random access I/O of data to/from disk files has the same kind of limitation, also probably for the same reason. It seems that the compiler could do this for you.

Another drawback of using Modula-2 becomes evident if you are writing a short program from which you want quick results. There is some significant overhead in typing in the numerous FROM SomeLibrary IMPORT SomeProcedure; statements necessary to do any useful task. For a while, we both returned to Turbo Pascal when we had to do something quickly. Eventually, as we became more involved with Modula-2, we evolved template main program modules that contain the imports (such as WriteLn, WriteString, Assign, Concat, and so forth) that we typically use in our work. However, there are still the frustrating times when you discover at compile time that you've forgotten to import one or more of the procedures you need.

The scarcity of commercially available software libraries for Modula-2 was not an insurmountable problem for us. We invested about six months in writing our own utility routines, including both a program to produce straightforward scientific plots and a useful set of generic field editing routines. We have also successfully used packages designed to be called from other languages, with more or less trouble depending on what memory model the library uses. One image-processing library written to be called from Microsoft Pascal required an assembly language interface module to rearrange the stack so that the arguments were passed correctly.{3

Conclusions

After extensive use, we have both become rather fanatical about Modula-2 and have standardized on it for all new development in the lab. We have hired undergraduate and graduate students for summer jobs who were able to learn the language quickly and produce modules for us in reasonably short periods of time. We have certainly met resistance from programmers unfamiliar with Modula-2, but we have adhered to our decision to maintain a single language.

In short, we feel that Modula-2 has delivered on its promise to be very suitable for multiprogrammer projects. Its modular structure has allowed us to write efficient, reusable code that can be used in a variety of applications. At this point, we're looking forward to more support, both commercial and user group.

REFERENCES

    1. "Modula II, An Overview -- Excerpts From A Talk By Niklaus Wirth," Micro Cornucopia (Aug/Sept 1985), 25.

    2. N. Wirth, "History and Goals of Modula-2," BYTE (August 1984), 145.

    3. C. Johnston, "Modula-2 in the Mainstream," Computer Language (June, 1989), p.71.

NASA does not endorse commercial products. Details about any products named in this article were included for completeness and accuracy. No endorsement or criticism of these products by NASA should be assumed. The source code for the entire package is available from the authors upon request.

_MAKING THE MOVE TO MODULA-2_ by J.V. Auping and Chis Johnston

[LISTING ONE]



DEFINITION MODULE ModPlot;

(* Title   : High level Modula-2 Graphics library interface
Author  : Judy Auping
System  : PC Graphics
Compiler: LOGITECH MODULA-2/86
*)

  FROM DataDefs IMPORT
    DeviceType,UnitsType,SizeType,AngleType,LineType,ColorType,
    FontType,OriginType,SymbolType,ModeType,STRING80;

  EXPORT QUALIFIED
    GraphInit,SetPlotDevice,SetPlotArea,SetScaledArea,SetScale,SetUnits,
    SetCharSize,SetLabelAngle,SetLineType,SetPenColor,SetBackgroundColor,
    SetLabelOrigin,SetFixedDigits,ReturnRatio,SetFontType,
    Draw,Move,IncDraw,IncMove,DrawXAxis,DrawYAxis,DrawAxes,DrawGrid,
    DrawLabel,DrawLabelledAxes,DrawLabelledGrid,DrawFrame,DrawSymbol,
    Where,NewScreen,CloseGraphics;

  PROCEDURE GraphInit;
     (* Initializes system-MUST be called before any output generated *)
  PROCEDURE SetPlotDevice(GraphDevice: DeviceType);
     (* Selects output device for subsequent graphics commands *)
  PROCEDURE SetPlotArea(XMin,XMax,YMin,YMax: REAL);
     (* Sets the 'clip' area in % of absolute device boundaries*)
  PROCEDURE SetScaledArea(XMin,XMax,YMin,YMax: REAL);
     (* Sets the area in % of absolute device boundaries to which
        subsequent SetScale takes effect*)
  PROCEDURE SetScale(XMin,XMax,YMin,YMax: REAL);
     (* Sets the user scale *)
  PROCEDURE SetUnits(GraphUnits: UnitsType);
     (* Sets either User (Scaled units) or Device (absolute units) *)
  PROCEDURE SetCharSize(CharSize: SizeType);
     (* Sets character size for subsequent labels *)
  PROCEDURE SetLabelAngle(LabelRotation: AngleType);
     (* Sets the angle of rotation of subseqent labels *)
  PROCEDURE SetLineType(LineTypeSelected: LineType);
     (* Sets the line type for subsequent Draw commands *)
  PROCEDURE SetPenColor(PenColor: ColorType);
     (* Sets the pen color for subsequent output *)
  PROCEDURE SetBackgroundColor(BackgroundColor: ColorType);
     (* Sets the background color (screen only) *)
  PROCEDURE SetLabelOrigin(LabelOrigin: OriginType);
     (* Determines orientation relative to current position with
        which subsequent labels will be drawn *)
  PROCEDURE SetFixedDigits(XNumDigits,YNumDigits: CARDINAL);
     (* Sets number of digits to right of decimal point for
        subsequent DrawLabelledAxes or DrawLabelledGrid *)
  PROCEDURE ReturnRatio(): REAL;
     (* Returns the ratio of the physical dimensions of the
        plotting area of the current device *)
  PROCEDURE SetFontType(FontTypeSelected: FontType);
     (* Sets the font type for subsequent label commands *)
  PROCEDURE Draw(XCoord,YCoord: REAL);
     (* Draw a line from the active position to the specified coords *)
  PROCEDURE Move(XCoord,YCoord: REAL);
     (* Move the active position to the specified coordinates *)
  PROCEDURE IncDraw(XIncr,YIncr: REAL);
     (* Do an incremental draw from the active position *)
  PROCEDURE IncMove(XIncr,YIncr: REAL);
     (* Do an incremental move to the new active position *)

  (* In the following set of axis drawing and labelling procedures,
     the variables XIntercept, YIntercept, XTicSpacing, YTicSpacing
     XMin, XMax, YMin, and YMax are all interpreted according to the
     most recent SetUnits, SetScaledArea, and Set Scale commands.
     MajorCount is an integer that specifies the number of tic intervals
     between major tic marks.  In DrawLabelledAxes and DrawLabelledGrid,
     if MajorCount is positive, tic marks are drawn perpendicular to the
     corresponding axis; if negative, tic marks are parallel.
     MajorTicFrac specifies the size of the major tics as a percentage
     of the length of the corresponding axis.*)

  PROCEDURE DrawXAxis(YIntercept,TicSpacing,XMin,XMax: REAL;
                  MajorCount: INTEGER;
                  MajorTicFrac: REAL);
     (* Draw an X-axis from XMin to XMax at the specified y-intercept*)
  PROCEDURE DrawYAxis(XIntercept,TicSpacing,YMin,YMas: REAL;
                  MajorCount: INTEGER;
                  MajorTicFrac: REAL);
     (* Draw a Y-axis from YMin to YMax at the specified x-intercept*)
  PROCEDURE DrawAxes(XIntercept,YIntercept,XTicSpacing,YTicSpacing: REAL;
                 XMajorCount,YMajorCount: INTEGER;
                 XMajorTicFrac,YMajorTicFrac: REAL);
     (* Draws full-scale axes intersecting at XIntercept and YIntercept*)
  PROCEDURE DrawGrid(XIntercept,YIntercept,XTicSpacing,YTicSpacing: REAL;
                 XMajorCount,YMajorCount: INTEGER;
                 XMinorTicFrac,YMinorTicFrac: REAL);
     (* Draws a full-scale grid, with lines spaced symmetrically
        around XIntercept,YIntercept *)
  PROCEDURE DrawLabel(LabelString: ARRAY OF CHAR);
     (* Writes LabelString at the current active position according
        to the current CharSize, LabelOrigin, and LabelRotation settings*)
  PROCEDURE DrawLabelledAxes(XIntercept,YIntercept,XTicSpacing,
                              YTicSpacing: REAL;
                              XMajorCount,YMajorCount: INTEGER;
                              XMajorTicFrac,YMajorTicFrac: REAL);
      (* Draws a pair of axes in the same manner as DrawAxes.  Puts
         labels at the major tic marks according to the current
         CharSize, XNumDigits, and YNumDigits settings *)
  PROCEDURE DrawLabelledGrid(XIntercept,YIntercept,XTicSpacing,
                              YTicSpacing: REAL;
                              XGridSpacing,YGridSpacing: INTEGER;
                              XMajorTicFrac,YMajorTicFrac: REAL);
      (* Draws a full-scale grid as in DrawGrid and labels the grid
         lines as in DrawLabelledAxes *)
  PROCEDURE DrawFrame;
       (* Draws a box around the current plotting area *)
  PROCEDURE DrawSymbol(XCoord,YCoord: REAL;
                       Symbol: SymbolType;
                       Size: REAL);
       (* Draws the indicated symbol centered at XCoord,YCoord.
          Size is specified in mm *)
  PROCEDURE Where(VAR XCoord,YCoord: REAL);
       (* Returns the coordinate values of the current active position*)
  PROCEDURE NewScreen(Mode: ModeType);
       (* Clears the screen and sets the mode. No cursor in Menu mode*)
  PROCEDURE CloseGraphics;
       (* Cleans up the interrupts and restores to normal *)
END ModPlot.





[LISTING TWO]


DEFINITION MODULE GDriver;
(* Title   : Low Level CRT and Plotter Draw module
Author  : Chris Johnston
System  : Modula-2 Plotting System
Compiler: LOGITECH MODULA-2/86
*)

  FROM DataDefs IMPORT ModeType, STRING80, DevPresentType;

    EXPORT QUALIFIED
      ReadDevices, DrawAbs, MoveAbs, DrawString, SetMode, CleanUp;

   PROCEDURE ReadDevices(VAR DevicesPresent : DevPresentType);
     (* Finds out what devices are available: EGA/CGA and
         IBM7372A/IBM7372B/IBM7371. An HP 7470 is reported
         as an IBM7371 and the HP 7475 is reported as an
         IBM7372A or B depending upon the paper size selected *)

   PROCEDURE DrawAbs(XCoord, YCoord : CARDINAL);
     (* Draw a line from the current location to XCoord, YCoord
         on the selected device. Line type and color read from system
         globals  *)

   PROCEDURE MoveAbs(XCoord, YCoord : CARDINAL);
     (* Move from the current location to XCoord, YCoord on the
         selected device with the pen raised. *)

   PROCEDURE DrawString(XCoord, YCoord : CARDINAL;
                        LabelString    : ARRAY OF CHAR);
     (* Draw the character string LabelString starting at XCoord, YCoord.
         The font, color, size, and rotation are selected from system globals
*)

   PROCEDURE SetMode( Mode : ModeType);
     (* Set the mode to text or graphics and clear the screen. This call has
         ** NO EFFECT ** if the selected device is a plotter *)

   PROCEDURE CleanUp;
     (* clean up the interrupt drivers and the character set at the end. *)

END GDriver.




[LISTING THREE]


DEFINITION MODULE DataDefs;

(* Title   : Data Definitions
Author  : Judy Auping
System  : PC Graphics
Compiler: LOGITECH MODULA-2/86
*)

  EXPORT QUALIFIED
    DeviceType,UnitsType,SizeType,AngleType,LineType,ColorType,
    FontType,OriginType,SymbolType,DevPresentType,ModeType,STRING80,
    GraphDevice,LineTypeSelected,CharSize,FontTypeSelected,
    PenColor,BackgroundColor,LabelRotation,DeviceXMax,DeviceYMax,
    ErrorString, DriverError;

  TYPE
    DeviceType = (EGA,CGA,IBM7372A,IBM7372B,IBM7371);
    UnitsType = (User,Device);
    SizeType = (Small,Med,Large,XLarge);
    AngleType = (Deg0,Deg45,Deg90,Deg135,Deg180,Deg225,Deg270,Deg315);
    LineType = (Solid, EndPoint, Dotted, ShortDash, LongDash);
    ColorType = (Black,Blue,Green,Cyan,Red,Magenta,Brown,White,
                 DarkGray,LightBlue,LightGreen,LightCyan,
                 LightRed,LightMagenta,Yellow,IntensifiedWhite);
    FontType = (Standard,Italic);
    OriginType = (UpperRight,CenterRight,LowerRight,UpperMiddle,
                  CenterMiddle,LowerMiddle,UpperLeft,CenterLeft,
                  LowerLeft);
    SymbolType = (Circle,Square,Triangle,Asterisk,Cross,Plus);
    DevPresentType = ARRAY DeviceType OF BOOLEAN;
    ModeType = (Graphics,Text,Menu);
    STRING80 = ARRAY[0..79] OF CHAR;

  VAR
    GraphDevice: DeviceType;
    LineTypeSelected: LineType;
    CharSize: SizeType;
    FontTypeSelected: FontType;
    PenColor: ColorType;
    BackgroundColor: ColorType;
    LabelRotation: AngleType;
    DeviceXMax,DeviceYMax: CARDINAL;
    DriverError: BOOLEAN;
    ErrorString: STRING80;

END DataDefs.





[LISTING FOUR]


MODULE Example;
(* Title: Example of using the ModPlot graphics library
Author: Judy Auping
System: PC Graphics
*)

  FROM DataDefs IMPORT
    DeviceType,SizeType,ColorType,OriginType;

  FROM ModPlot IMPORT
    GraphInit,SetPlotArea,SetScaledArea,SetScale,DrawAxes,SetPenColor,
    Move,Draw,DrawFrame,NewScreen,CloseGraphics,SetLabelOrigin,DrawLabel,
    SetCharSize,IncDraw,SetPlotDevice;

  FROM MathLib0 IMPORT sin;

  FROM InOut IMPORT Read,WriteString,WriteLn;

TYPE
  CornerType = (UpLeft,UpRight,LowLeft,LowRight);

CONST
  NPnts = 1000; pi = 3.14159;

VAR
  XValue: ARRAY[1..NPnts] OF REAL;
  YValue: ARRAY [UpLeft..LowRight] OF ARRAY[1..NPnts] OF REAL;
  NumTerms: ARRAY[UpLeft..LowRight] OF CARDINAL;
  IPlot: CornerType;
  IPnt,ITerm: CARDINAL;
  x,NextTerm: REAL;
  Input: CHAR;

PROCEDURE GeneratePlotArrays;
(* This procedure generates arrays of data points for the Fourier series
   approximation of a sawtooth wave, where

       y = 2 (sin x - sin(2x)/2 + sin(3x)/3 - sin(4x)/4 + ... )

   The XValue array contains the x values for the plot in units of pi, where
   the values vary from zero to  4pi.

   The YValue array of arrays contains four arrays of y values for
   different numbers of terms in the summation approximation. *)

BEGIN
  WriteString("Generating approximation functions"); (* Inform user *)
  NumTerms[UpLeft] := 5;     NumTerms[UpRight] := 10;
  NumTerms[LowLeft] := 20;  NumTerms[LowRight] := 100;

  FOR IPnt := 1 TO NPnts DO

    IF (IPnt MOD 100)=0 THEN
      WriteString(" ."); (* Let the user know the progress of *)
    END (* if *);        (* the calculations.*)

    FOR IPlot := UpLeft TO LowRight DO
      YValue[IPlot,IPnt] := 0.0;  (* Initialize the terms. *)
    END (* for *);

    x := FLOAT(IPnt) * (4.0 * pi)/FLOAT(NPnts);
    XValue[IPnt] := x/pi;

    FOR ITerm := 1 TO NumTerms[LowRight] DO
      IF (ITerm MOD 2)=0 THEN (* even terms are negative *)
        NextTerm := -2.0 * sin(FLOAT(ITerm)* x)/FLOAT(ITerm);
      ELSE                    (* odd terms are positive,*)
        NextTerm := 2.0 * sin(FLOAT(ITerm)* x)/FLOAT(ITerm);
      END (* if *);

      FOR IPlot := UpLeft TO LowRight DO
        IF ITerm<=NumTerms[IPlot] THEN
          YValue[IPlot,IPnt] := YValue[IPlot,IPnt] + NextTerm;
        END (* if *);
      END (* for *);
    END (* for *);
  END (* for *);
END GeneratePlotArrays;

BEGIN
  GeneratePlotArrays;
  GraphInit;
  SetPlotDevice(IBM7372A);
  WriteLn; WriteString("Drawing plot . . ."); (*Let user know where we are*)

  FOR IPlot := UpLeft TO LowRight DO  (*Draw a plot for each array*)
    CASE IPlot OF (* For each array, choose the appropriate plotting area*)
      UpLeft:SetPlotArea(0.0,45.0,60.0,100.0);  (*Upper left corner *)
           SetScaledArea(5.0,40.0,62.0,94.0);
     |UpRight: SetPlotArea(55.0,100.0,60.0,100.0); (*Upper right corner*)
           SetScaledArea(60.0,95.0,62.0,94.0);
     |LowLeft:SetPlotArea(0.0,45.0,0.0,40.0); (*Lower left*)
           SetScaledArea(5.0,40.0,2.0,34.0);
     |LowRight:SetPlotArea(55.0,100.0,0.0,40.0); (*Lower right*)
           SetScaledArea(60.0,95.0,2.0,34.0);
    END (* case *);

    SetScale(0.0,4.0,-4.0,4.0); (* remember, x is in units of pi *)
    SetPenColor(Black);
    DrawAxes(0.0,0.0,1.0,1.0,2,2,3.0,2.0); (* draw axes without labels *)
    (* Labels are drawn separately so we can put 'pi' on x-axis labels*)
    SetCharSize(Small); (* Label the axes *)
    SetLabelOrigin(CenterLeft); (* First, the y-axis *)
    Move(-0.05,4.0); DrawLabel("4");
    Move(-0.05,2.0); DrawLabel("2");
    Move(-0.05,0.0); DrawLabel("0");
    Move(-0.05,-2.0); DrawLabel("-2");
    Move(-0.05,-4.0); DrawLabel("-4");

    SetLabelOrigin(LowerMiddle); (* Then the x-axis *)
    Move(2.0,-0.5); DrawLabel("2pi");
    Move(4.0,-0.5); DrawLabel("4pi");

    CASE IPlot OF (*Set a new pen color for each plot *)
      UpLeft: SetPenColor(Red);
     |UpRight: SetPenColor(Green);
     |LowLeft: SetPenColor(Blue);
     |LowRight: SetPenColor(Magenta);
    END (* case *);

    Move(0.0,0.0); (* Start at the origin *)
    FOR IPnt := 1 TO NPnts DO
      Draw(XValue[IPnt],YValue[IPlot,IPnt]); (*Draw to each point*)
    END (* for *);

    SetPenColor(Black);
    DrawFrame; (*Draw a box around the plot for this array *)

    Move(0.75,4.7);
    SetLabelOrigin(CenterRight);
    SetCharSize(Small);
    CASE IPlot OF  (*Put the appropriate title on each plot*)
      UpLeft: DrawLabel("5 terms in series");
     |UpRight: DrawLabel("10 terms in series");
     |LowLeft:DrawLabel("20 terms in series");
     |LowRight:DrawLabel("100 terms in series");
    END (* case *);
  END (* for *);

  (*Now that all four plots have been drawn, put a title and
    the formula in the middle area on the page *)
  SetPlotArea(0.0,100.0,0.0,100.0); (* Set to full screen *);
  SetScaledArea(0.0,100.0,0.0,100.0);
  SetScale(0.0,100.0,0.0,100.0);

  Move(9.0,53.0);
  SetCharSize(Med);
  SetLabelOrigin(CenterRight);
  DrawLabel("FOURIER SERIES APPROXIMATION TO A SAWTOOTH WAVE");
  Move(17.0,47.0);
  SetCharSize(Small);
  DrawLabel("y = 2 {sin(x) - sin(2x)/2 + sin(3x)/3 - sin(4x)/4 + ... }");

  CloseGraphics; (* Clean up and restore the system *)
END Example.









Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.