Jump to content
IGNORED

Code library management in Pascal


apersson850

Recommended Posts

If you haven't read the threads about memory management for code and data in the p-system, using Pascal, then you should, prior to reading this post. After that, you'll understand that the p-system supports pretty complex software.

 

When writing larger programs, you frequently find that you want to re-use functions you've already written before. To begin with, the p-system already from the beginning contains some such commonly used functions, which you can simply reference in your own program, and then use them just like if you have written the code yourself. In the p-system, this allows you to access some functions that are specific for the 99/4A, like sound processing, sprite handling and character pattern definitions.

 

The standard library

To manage this in a simple way, the p-system supports a library function. Programs can be written to be included in a library, which you then can use, even though they aren't inside your own program. The p-system contains a file called *SYSTEM.LIBRARY. Inside that file, which resides on the system disk (*file means a file on the system disk), are the supporting units supplied with the system. The SYSTEM.LIBRARY file contains several units from start.

 

Assume you want to place an asterisk at the center of the screen. This program will do that.

 

program display1;

begin
  gotoxy(20,12);
  write(chr(42));
end.

Now assuming you want a different shape, one that's not available among the standard characters, then you want to redefine the character. The 99/4A does allow for that, but the standard Pascal language has no support for such machine specific things. Here the predefined library function set_pattern comes to resuce. It will do the same things as CALL CHAR in BASIC. Consulting the compiler manual, we find that this function resides in the unit support in the system library. To get access to it is as simple as saying you want to use functions in that library, and then just use it as if it was built into Pascal.

 

program display2;

uses support;

begin
  set_pattern(42,'0081422418244281');
  gotoxy(20,12);
  write(chr(42));
end.

When the compiler creates the code file from this source code, it will see that you want to use the functions available in the support unit. As you have not told the system where to find support, it will look for it in *SYSTEM.LIBRARY. It is there, so that will work. The compiler will then generate code to define that the routines referred to in support are located in the *SYSTEM.LIBRARY file. When execution comes to set_pattern, an external segment call is done. The operating system will know, from data in the code file, that it needs to load a code segment which is not inside the program running now, but in another file. The support unit will be loaded from the library into memory, where it can be executed. This involves accessing the disk where the library is residing. But all this happens automagically. The only thing needed is the single line uses support in your code.

If you have more than one set_pattern call in your program, you'll not notice. Upon the second call, the unit support is already memory resident, so it's called just like a routine in your own program.

 

As you can see, then handling is identical to that of internally declared segments in your own code, which I described in a previous thread. Indeed, as a minimum, each unit in a library is a separate segment. The *SYSTEM.LIBRARY file contains seven units from the beginning. Regardless of how many you use, they are only loaded into memory when needed.

Look at this program, which uses two units.

 

program display3;

uses
  support,
  speech;

type
  longstring = string[255];

procedure waitlong;

var i: integer;

begin
  for i:=1 to 32000 do;
end;

begin
  gotoxy(20,12);
  write(chr(42));
  waitlong;
  set_pattern(42,'0081422418244281');
  waitlong;
  say('Hello');
end.

 

The program will first output the * symbol at the center of the screen. It will then spend several seconds in an empty loop. If you are loading the program from a physical diskette drive, the drive will have time to stop.

After a while, the drive which contains the *SYSTEM.LIBRARY file will start up, since the program has reached the set_pattern call. The unit support is not in memory at that time, so it requires loading. You'll see the * change shape when this happens.

The program will then wait again, long enough for the drive to stop once more. Suddenly, it will start again, when the program reaches the say statement. Unit speech is at this time not loaded, so the access to the library is redone. When it's ready, the computer will speak Hello.

If you once again use eXecute to run this program, the sequence will repeat. If you instead use the User restart command, the program will run through the code without starting any diskette drive to load the library routines. User restart preserves the execution environment, incluing loaded internal and/or external segments, so the program can run from start to end without any need for external access of various supporting software.

 

Already as-is, this is a powerful tool to make your programs able to do more things. But it doesn't end there. You can create your own library units, append them to the *SYSTEM.LIBRARY and/or create your own libraries, which can be used in combination with the system's library function. We'll look into that in follow-up posts in this thread.

Edited by apersson850
  • Like 6
Link to comment
Share on other sites

Large source files - small system

Pascal is a compiled language. All programs have at least two files. The source file, with the text you write when you create the program, and the code file. The latter is created by the compiler, which reads the source file and converts it to something the computer can execute.

 

In a small computer, there are limits to how big text files you can handle. The editor available for the 99/4A requires the whole text to be in memory at the same time. Assuming you have read the posts about memory management, you can understand that the editor uses all tricks in the book to free up as much memory as possible for the text. The result is that the p-system's editor can handle texts of around 12000 characters.

 

(* Program file A *)
program a;

(* All declarations and subprograms... *)

begin
(* ... combined with the main program must be no more than about 12000 characters. *)
end.

This is what the editor can handle.

Now, if you want to write a larger program, then it's still possible. You have to split it up in several files, which have to be edited separately. These extra files can then be included in the main program.

 

(* Compile this file. It will include the rest *)
program a;
(*$I progdecl.text *)
(*$I progproc.text *)

begin
(*$I progmain.text *)
end.

In this program the "main" file is just a collector of the real stuff. Everything that really accomplishes anything is spread out on three files. One with type and variable declarations, one with procedures and functions and the third with the main program code.

This approach works, but has a few drawbacks. One is of course that you have to shuffle files back and forth as you edit your program. If you are making something in the main program when you realize you need a new global variable, you have to save what you are doing, load the declarations file, update and save that and reload the main program. Or make notes about what you are going to do later.

So it takes some discipline to make this work smoothly.

The other drawback is compile time. Even if you only change a single line in the procedure declaration file, you have to compile the whole program all over again to test your edited program.

 

As one can suspect, there is a different method, which makes handling large source files easier. Hang on...

Edited by apersson850
  • Like 3
Link to comment
Share on other sites

Reusing code

Some tasks are recurring in the programming business, just like in many other activities. You may frequently find yourself writing code like this.

 

program one;

procedure a;
procedure b;
procedure c;
procedure d;

procedure e;
procedure f;
procedure g;
procedure h;

begin
  (* Main *)
end.
program two;

procedure a;
procedure b;
procedure c;
procedure d;

procedure i;
procedure j;
procedure k;
procedure l;

begin
  (* Main *)
end.

Here, four of the procedures used are general enough to be reused in the second program. An obvious way to handle that is to extract them from the main file and put the source code for them in a separate file, which you then just include in your main program.

 

program one;

(*$I goodstuff.text *)

procedure e;
procedure f;
procedure g;
procedure h;

begin
  (* Main *)
end.

program two;

(*$I goodstuff.text *)

procedure i;
procedure j;
procedure k;
procedure l;

begin
  (* Main *)
end.

This relieves you from having to handle the common source code in every program. Write it once and just include it. It also makes it possible to fine tune the performance of the good stuff only once. All programs compiled after that moment will benefit from your improvements. But you do have to recompile every program to make use of the change.

 

Separate compilation

Although the idea of including additional files in the main source file resolves the size problem of the source itself, it doesn't make the compiler's task any easier. For the compiler, it's still a bigger and bigger program to compile. Eventually, memory limitations will prevent the compilation of the program. There's not enough space for the symbol table etc.

In such a case, it would have been better if it was possible to compile the things in goodstuff separately, then just use them from the main program. Use them... That sounds familiar from the discussion about the system's library. To handle the speech synthesizer, we did not include any files for that. We just stated uses speech. The trick is of course to create the library program to be used.

Fortunately, this is nothing that's for the system to use only. You can take advantage of the same mechanism.

 

Pascal in the UCSD p-system supports something called a unit. The unit is like a program. It has a header, procedure declarations and can have a main program body. But it's not intended to be executed on it's own. It should be used by other programs.

 

Before we look at the details of how to create a unit, we look at how to use it. Here is the example above, where we assume that the four procedures we put in the goodstuff file are now instead in a separately compiled unit.

The program would now look like this:

 

program three;

uses goodstuff;

procedure e;
procedure f;
procedure g;
procedure h;

begin
  (* do things *)
  e; (* Call procedure declared in this program *)
  a; (* call procedure hosted by unit goodstuff *)
end.

In the main program, there's no difference between calling a procedure declared in the program itself or a procedure that's residing in the goodstuff unit.

So how do we make a unit?

 

unit goodstuff;

interface

type
  goodarray = array[0..100] of integer;
var
  goodx, goody, goodz: integer;

procedure a(p: integer);
procedure b(f: integer, list: goodarray);
procedure c(a,b: string);
procedure d(x:real);

implementation

var
 internal_a, internal_b: integer;
 cool: goodarray;

procedure x(a,b,p: integer);

procedure a;
begin
  (* code *)
end;
procedure b;
begin
  (* code *)
end;
procedure c;
begin
  (* code *)
end;
procedure d;
begin
  (* code *)
end;

begin
  (* Init code *)
***;
  (* Exit code *)
end.

Here is the unit goodstuff. It has two major parts. The interface part is what's visible to other programs. Anything listed here will be visible to the compiler when it encounters the uses goodstuff line. You can see that in addition to the declaration of the procedures a, b, c and d, there are also a data type and a few variables that are externally accessible. Your program can use these variables and data type in the same was as if you had declared them in your program. The difference is that the code in the unit goodstuff can also use them. They are a shared resource. Note that there are only declarations in the interface part. No real action takes place here. There is no code in the procedures, just the declaration line, which defines the name and the parameters passed to the procedure.

 

The next major part of the unit is the implementation part. Variables declared here are global, but only within the unit goodstuff. They are neither visible nor accessible from the program that uses the unit. Then comes the procedure declarations. Here, only their name is given. The parameter list must not be repeated. It's already defined in the interface part, and there must be one definition only. On the other hand, the code in the procedures is defined here. Note that there is also a procedure x in the implementation part. It can be called from the code defining the procedures inside the unit, but procedure x can't be called from the outside. You can add more procedure, or change their name, in the implementation part without affecting the program that uses the unit.

 

Finally, there's a minor part at the end. It may be omitted, but if it exists, it can contain init and/or exit code for the unit. If the unit needs to set up some internal data, prior to calling the unit's procedures, it can be done here. Likewise, if something needs to be cleaned up on exit, it can also be done here. The odd command ***; defines the border between startup and exit code. Startup code in all referenced units is executed before the main program starts, and exit code runs after the main program has stopped.

 

Benefits of units

Units bring several advantages.

  • Separate compilation. You compile the unit once. When you use it, the compiler only needs to look in the interface part, to find out what's available in the unit. It doesn't have to spend time compiling all the code in the implementation part of the unit. This makes compilation of the program that uses the unit faster.
  • Reusability. An unlimited number of programs can use the unit. You only have to refer to it, and that's it. No need to manage source code files with the functionality you need in your different programs.
  • Independence. You can make changes to the implementation part of a unit without having to re-compile the programs that uses it. As long as the changes are done only in the implementation part, the other programs that are using it, will benefit from that, regardless of whether they were compiled before the change, or will be compiled after it. It's only if you need to change the interface part you need to re-compile the programs using the unit.
  • Linked dependencies. A unit can use another unit. You can make a more advanced unit, which in turn uses your more basic units.

Here we assume we have made another unit, which implements the other four procedures in program three.

 

program four;

uses allweneed;

begin
  (* do things *)
  e; (* Call procedure declared in allweneed *)
  a; (* call procedure hosted by unit goodstuff *)
end.

Note that we still call procedures that are in goodstuff, but we don't mention it. For this to work, the unit allweneed must be declared like this:

 

unit allweneed;

interface

uses goodstuff;

procedure e;
procedure f;
procedure g;
procedure h;

implementation

var
 internal_a, internal_b: real;
 hot: goodarray;

procedure y(a,b,p: integer);

procedure e;
begin
  (* code *)
end;
procedure f;
begin
  (* code *)
end;
procedure g;
begin
  (* code *)
end;
procedure h;
begin
  (* code *)
end;

begin
  (* Init code *)
***;
  (* Exit code *)
end.

The important thing here is that the command uses goodstuff is in the interface part. It doesn't have to for the unit allweneed to work, but if it wasn't, it would not be possible to call the procedures a, b, c and d directly from program four.

 

If the unit allweneed is declared like this, then program four must either refrain from calling the procedures in goodstuff directly, or contain the code uses goodstuff, allweneed; This is perfectly legal, as a program can use many units at the same time. Note that neither the procedures nor the data structures in goodstuff are available to the main program with this design. Not without referring to them from the main program too.

 

unit allweneed;

interface

procedure e;
procedure f;
procedure g;
procedure h;

implementation

uses goodstuff;

var
 internal_a, internal_b: real;
 hot: goodarray;

procedure y(a,b,p: integer);

procedure e;
begin
  (* code *)
end;
procedure f;
begin
  (* code *)
end;
procedure g;
begin
  (* code *)
end;
procedure h;
begin
  (* code *)
end;

begin
  (* Init code *)
***;
  (* Exit code *)
end.

With some planning, this means that we have a tool which makes compilation of the frequently used routines simpler, at the same time as it does the same to the programs that uses these routines.

On top of that we can get independence between the support programs and the programs that use them, so that the support programs can be improved without the clients knowing about the changes.

 

Combined with the functionality the operating system provides to make these general routines available to the user, this becomes a very powerful development tool. We have already touched base with how to use the units provided in the system's library. In the next post, we'll look at how you can expand the library with your own, or other people's units.

Edited by apersson850
  • Like 5
  • Thanks 1
Link to comment
Share on other sites

Where is the unit?

When using separately compiled units, they have to be found. There are two different occasions when a unit must be located.

 

Compile time

When the program is compiled, the compiler must find the unit. The simplest way is if the unit is residing in the *SYSTEM.LIBRARY file. That's the default location where the compiler will look.

However, if you create a program that contains a few units, but they are specific for that particular program, then you don't want to add them to the *SYSTEM.LIBRARY file. You want to keep them in their own files.

To tell the compiler where to find them, you include the file name in your source code.

Here are a few lines from a program. It's the first four unit references. Note that the first unit called is a system unit, so no file name need to be specified. But the following units all have their filename given explicitly.

uses
   crt,
   (*$U dustglob *) dustglobal,
   (*$U dustent  *) dust_entry,
   (*$U dustshow *) dust_show,

 

Run time

When your program executes, the unit must also be located. Again, the simplest way is hosting it within the *SYSTEM.LIBRARY file. Then everything works automatially.

But you can also have your unit(s) in other files. Like when they are specific for a certain application.

 

If the unit is in a separate library, not the system's library, you need to tell the runtime system where it is. This is done by creating a text file, which holds the names of the library files.

The default name for this list of units is *userlib.text. The name can be changed by an execution option, though.

If you have three library files outside the system's library, then the file *userlib.text may have this content.

*service
develop:mathlib
timing:timerlib.code

One library is on the system disk, the other two on named disks. When you start your program, the system will look for your units in these files, then search *SYSTEM.LIBRARY. If it can't be found, your program will fail starting.

 

A third method is to include the unit in the same code file as your main program. This is especially handy if you want to send your program to somebody else, since then all required units are within the same file. To accomplish this, the p-system provides a special tool.

 

The LIBRARY utility

On the Utilitites disk, there is the LIBRARY program. As one can suspect, it's used to manage code libraries.

With this program, you can do several different (although related) things.

  • Group your own units together in a library.
  • Add or remove units from an existing library. This way you can add things to the *SYSTEM.LIBRARY, or remove things to save space.
  • Add assembly routines to a library, which can then be found in the library if you need linking them to your own program. Handy if you have an all-assembly support you want to link to many programs. Frequently it's easier to put that assembly code inside the implementation of a unit instead.
  • Combine your own program with your own units, and perhaps segments, to a stand-alone code file, which has all the support it needs embedded within itself. This may also include embedding system units, like SPRITE or SOUND, if you want to use these effects in your program. Then there's no demand for those system support units to be available in a system library at run time.

As you can see, the libraries in the p-system are a very powerful tool, when you want to develop more complex software.

 

The largest program I've written myself on the TI 99/4A was about 4000 source code lines. That program references seven units, written for the application, and one system unit. It runs on the 99/4A, with room for meaningful data still available.

  • Like 3
  • Thanks 1
Link to comment
Share on other sites

The library and unit concepts are very useful. There are also other ways than the most obvious to use it.

 

An example is when I want to display Swedish text on the sceen. The common method back then was to change the definition of some characters, so that [\] became ÄÖÅ, for example.

 

You can of course use the support unit and the procedure set_pattern to do that.

But it's more convenient to define a unit that does this. Then you can just call a procedure that does all the pattern changes for you.

The flexibility of the UCSD p-system makes this even more simple. Since you can specify both startup and exit code, which runs when the unit is referenced and when the user stops, respectively, it's even more simple.

 

Just add the line uses swedish; to your program, and that's it. Character pattern change and return to standard is as easy as that. You don't even have to tell it to go back to English explicitly.

  • Like 4
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...