PRINCIPLES OF 

ASSEMBLY LANGUAGE PROGRAMMING

 

DAVID J. KRUS

ARIZONA STATE UNIVERSITY

 

CRUISE SCIENTIFIC

Taipei Phoenix Amsterdam

 Edited by: James M. Webb

Designed by: Sie Han-Yen

Technical Advisor: Pei Sie-Ka

Information in this document is subject to change without notice. The software described in the text is not copy protected. However, both this manual and the software it describes are protected by copyright and should not be copied without the written consent of the publisher for any purpose other than the purchaser's personal use.

Microsoft is a registered trademark of Microsoft Corporation. Hewlett Packard is a registered trademark of Hewlett Packard Company. The Cruise Fortran and LaserJet Libraries are listed in the Microsoft Language Support Directory and in the Hewlett Packard Peripherals Catalog.

The development of the Fortran callable assembly language subroutines for control of HP LaserJet printers was made possible by the Hewlett Packard Peripherals Developer Program, providing a series of LaserJet printers for the final hardware tests. Sincere thanks are extended to Rachelle Wheatley and Lisa Menas, coordinators of the HP Peripherals Developer Program, for their help and support which contributed to the successful completion of this project.

  

Copyright 1997 by Cruise Scientific

5F, 11, 10, 329 Nei Hu Road

Taipei, Republic of China

 

In the Americas address inquiries to Cruise Scientific

3434 West Tulsa Street, Chandler, Arizona 85226-4035

 

In Europe address inquiries to Cruise Scientific

52 Zamenhofdreef, Utrecht, Netherlands 356JZ

  

Cover Art by Naoki Yoshimoto.

 

International Standard Book Number: 0-940325-20-9

  

Manufactured in the Republic of China

 

TABLE OF CONTENTS

  

CHAPTER

ONE

 

INTRODUCTION 1

Securing and Displaying Information - Windows - Programming at DOS, BIOS and Hardware Levels - The Keyboard - Disks and Disk Files - Coordination of Program Execution - Control of Laser Printers - Getting Started

 

CHAPTER

TWO

 

SYSTEM CONFIGURATION AND ORIENTATION IN TIME 7

Type of the Central Processor Unit - System Memory - Upper Memory - Memory Available to Program - Version of the Operating System - Presence of ANSI.SYS Driver - Retrieve Date and Day of the Week - Date Composition - Measure Elapsed Time - Delay Program Continuation

 

 

CHAPTER

THREE

 

THE KEYBOARD 15

Sensing the State of Caps Lock and and Num Lock Keys - Unlocking Caps and Keypad Keys - Redefine Keyboard Key Assignments - Input at the Level of DOS Interrupts - Input at the Level of BIOS Interrupts - The Mapped Keyboard Input - Secure Response to a Query

 

CHAPTER

FOUR

 

SCREEN DISPLAYS 22

Clear Screen - Reverse Screen - Blinking Message - Invisible Password Entry - Redefine Cursor Shape - Locate Cursor - Reposition Cursor - Frame a Screen Display Area - Screen Background Textures - Define Attributes of the Scrolled Screen - Cover Page Display - Pagination of Screen Displays - Windows - Read Numbers from Screen - Write Numbers to Screen - Read Text from Screen - Write Text to Screen - Write Text to Screen via DOS Calls - Write Text to Screen via BIOS Calls - Test Strings for Numbers - Fill String with Blank Spaces - Format Strings - Measure Length of Text within String - Text Strings in Upper and Lower Case - Separating Thousands in Numerical Strings

 

CHAPTER

FIVE

 

DISKS AND DISK FILES 44

How Many Drives are Present? - Where are We? - Where to Go? - Drive Description - How to Get There? - Change Directory - Remove Directory - Create a New Directory - View Directory - Delete a File - Read Only Files - Make File Invisible to Directory Search

 

CHAPTER

SIX

 

 

PROGRAM OPERATION AND COORDINATION 53

Command Line Arguments - Extract Parameters from the Environment - Invoke Operating System Commands - Catenate Programs - Program Termination

 

CHAPTER

SEVEN

 

 

THE SOUND 58

Sound Alert - Tones - Tunes - Phasers - Sound Effects

 

CHAPTER

EIGHT

 

 

ASYNCHRONOUS COMMUNICATIONS 61

Initialize a Serial Port - Transmit via Serial Port - Receive via Serial Port

 

CHAPTER

NINE

 

SELECTORS, MENUS, AND ELECTRONIC BLACKBOARDS 64

Design of the Central Menu - Vertical Free Floating Menus - Selectors - Electronic Blackboards

 

 

CHAPTER

TEN

 

CONTROL OF LASER PRINTERS 70

Opening and Closing Communication Channels - Escape Sequences - Transmission of Characters - Text Transmission - Printing Using the Row Column Grid - The Top Margin - Number of Lines per Page - Reset Margin Settings - Left and Right Margins - Wrap-Around Enable and Disable - Perforation Skip - Backspace - Cursor Movement Across Rows and Columns - Cursor Positioning - Horizontal and Vertical Cursor Movements - Horizontal Pitch - Vertical Spacing - Vertical Motion - Halfway Shifts Up and Down - Subscripts and Superscripts - Underlining - Carriage Return - Line Feed - Carriage Return Followed by Line Feed - Form Feed - Ejection of Page - Reset Printer Setting

 

CHAPTER

ELEVEN

 

 

SPECIAL FEATURES OF THE LASERJET 92

Number of Copies - Orientation - Simplex Printing - Duplex Printing - Binding Edge - Page Break - Paper Trays - Manual Paper Feed - Page Size - Envelopes

 

CHAPTER

TWELVE

 

 

CHARACTER GRAPHICS 99

Horizontal Line - Vertical Line - Coordinates of the XY Graph

 

CHAPTER

THIRTEEN

RASTER GRAPHIC 103

Resolution of Raster Graphics - Definition of the Paint Brush - Initiation of the Raster Graphics Transmission - Transmission of Raster Graphics - Termination of Raster Graphics - Creation of Raster Graphics - Horizontal Line - Vertical Line - Logo Design - Font Design

 

 

APPENDIX

 

ENVIRONMENT FOR PROGRAM DEVELOPMENT 225

Clavis - ASCII and Upper ASCII Templates - Screen Design Templates - Keyboard Codes - Program Development Aids

 

TABLES

 

TABLES FOR PROGRAM DEVELOPMENT 229

The ASCII Chart - The Chart of Design Characters - The BIOS Keyboard Scan Codes - Keyboard Codes Returned by Hardware Port 60h - Selected DOS Interrupts - Selected BIOS Interrupts

  

GLOSSARY 243

  

INDEX 259

  

CHAPTER

1

 

 

INTRODUCTION

 

Design of a modern computer program is a complex task which requires a substantial amount of knowledge of programming in a high level language and typically, also in assembly language. Aside of the development of the functional components of the program, correct structuring of the program components and their integration into a functional whole is crucial to the success of the programming project.

There are several excellent books of the program design, offering invaluable insights into the art of computer programming. Code defensively. Remember the Murhpy's dictum that if anything can go wrong, it will. Learn how to conceal complexity. Each subroutine should do one thing well and hide it, at the same time making the interface between the calling routine and itself highly visible. Find the optimal dividing line between interfacing subroutines, and communicate as few parameters, as possible. Avoid multiple exits from loops. Make sure your variables are initialized. Use meaningful variable names. Eschew temporary variables. Associate the decision points with their corresponding action codes as closely as possible. Do not write long subroutines. If a subroutine stretches over 3 or 4 full screen displays, it is probably too long. Localize input and output subroutines. Identify invalid input and make sure input does not violate program boundaries. Document your program and remember, that a program which runs slowly is far superior to a dysfunctional program running fast. These are maxims to be followed. However, the key difficulty in learning modern program design is that the global program design is developed from the top down, while the actual code has to be written from the bottom up. For the beginning programmer, faced with necessity to code component routines of the programming project, it is easy to loose track of the overriding concerns of the total design and to continue the development of the program along lines which ultimately lead to dysfunctional programs plagued by programming flaws next to impossible to remove.

The organizing principle of the present book is to help the beginning programmer to be aware of the requirements of the overall program design by initially presenting program fragments of calling statements to a variety of library subroutines, to make him or her to be aware of the programming possibilities a comprehensive set of library subroutines offers. Subsequently, the programmer can attempt the development of his or hers own assembly language subroutines, callable from the Fortran programs.

There are several libraries extending Fortran available, the discussion in this book will use the Cruise Fortran Libraries to describe programming concepts and tasks which can be used in the course of the program design and development. The use of the Cruise Libraries does not mean that you should not use other libraries, quite on the contrary. Even though the contents of many dedicated Fortran libraries overlap, typically every library contains many unique subroutines, extending the range of your programming possibilities. By associating several outside libraries with your program design, the range of your programming possibilities widens. Initially, however, let us look at the overall structure of the Cruise Fortran Library.

 

 

SECURING AND DISPLAYING INFORMATION

 

The Cruise Fortran Library replaces Fortran read and write statements with the reads, readn, and writes, writen subroutines to secure and display the textual or numerical information. The reads stands for read string, readn for read number, writes for write string and writen for write number. As different from the Fortran sequential read and write statements, the reads, writes, readn, and writen screen i/o statements can be executed in any order, both temporal and spatial. They also provide for definition of attributes of displayed information and for flexible conversion between numbers as entities which can be algebraically manipulated and entities, suitable for screen displays. They help you to think about numbers in a way you write them on a piece of paper and in a way you calculate them in your head, which are not the same kinds of numbers they appear to be.

The arguments of the reads, readn, writes and writen subroutines have similar syntax. The first argument defines the attributes of the display. The basic attributes are normal, reverse, highlighted, blinking, and their combinations. The following arguments define the offset of the input or the output operations, using the screen row and column coordinates. The fourth argument specifies the format and the last argument provides for transmission of the information to be retrieved or displayed.

 

 

WINDOWS

 

The window subroutine is the key subroutine of modern program design. It was developed following the growing awareness among programmers that the screen displays are windows into the computer memory and that the size of this window, identical to the size of the computer monitor is only one of many possible. By enlarging the area of the memory allocated to the screen displays they realized that more than window can be opened at the same time and by varying the size of these new windows, the modern computer design was born.

When you open a window, you have to first specify its size. The area on the screen, corresponding to the window, is read into memory, saved, and the window surface is cleared. Subsequently, you may write into the window area. When you close the window, the area of the window is read into the memory, saved, and the screen is restored into its original state. All of that is happening at speeds far higher than the normal screen i/o operations, thus creating an illusion that a window is laid on the top of the original screen display. By saving and restoring multiple windows, you may create displays simulating opening of a book and perusing its pages, movable menus, an majority of the paraphernalia typifying the modern screen displays making computing user friendly, flexible, complex, structured, and beautiful.

The reads, readn, writes, and writen are coordinated with the window subroutine to allow you to do all of that easily, efficiently, and sufficiently fast. Remember, it is all done with mirrors and lights, but the key ingredient is the speed which only coding in the assembly language can provide.

 

 

PROGRAMMING AT DOS, BIOS, AND

HARDWARE LEVELS

 

The speed of computing is directly related to the level of interrupts used for development a subroutine. The lower the level, the faster the subroutine. However, there is a price to be paid for the speed and the decision which level to use in which situation is one of the most important to be made in the course of the development of the programming design. There are three main interrupt levels, indicated by the a b or c prefix, as, e.g., a_name, b_name, c_name, where name symbolizes the name of a particular subroutine. The a prefix indicates that the DOS interrupts were used; the b prefix signifies the BIOS interrupts. Subroutines prefixed by the letter c utilize directly the hardware ports. This explicit typing is used when alternate subroutines operating at the different level of interrupts are provided and the decision about the interrupt level to use rests with the programmer.

Most of the subroutines are typed implicitly. In the case of the key subroutines used for design of screen displays, the windows work on the C level, addressing the screen microprocessor directly. The reads, writes, readn and writen subroutines operate either on the B level which is the preferred one, or, sometimes, ascend to the A level, when the nature of the task requires it. This selection of the level is done automatically and does not require the programmer's attention.

If the selection of the level of interrupts to be used is to be done by the programmer, the level of interrupts used is stressed in the description of the alternative subroutine. Thus, e.g., for the screen displays, the subroutine a_pen operates at the A level and the subroutine b_pen at the B level. The alternate subroutines for screen displays carry less overhead than the more general reads, readn, writes, writen subroutines and can be used for more specialized tasks. In selection of the level, one may consider that the direct hardware access subroutines (the C level) are the fastest while the level A subroutines are the slowest with the BIOS B level subroutines located in between. However, as usual, one trades the increased speed of execution for increased hardware dependency. Also, the DOS interrupts provide for more functions than the BIOS interrupts and BIOS interrupts are by far more varied than the interrupts accessing the hardware directly.

 

 

THE KEYBOARD

 

A large group of subroutines are the subroutines for the control and redefinition of keyboard. The keyboard can be imagined as consisting of three levels. The surface level, associated with the DOS keyboard interrupts and the ANSI keyboard driver programmed by the escape sequences, the intermediate level controlled by the BIOS interrupts, and the deep level which can be controlled by the in/out assembly language calls, accessing directly the pins of the keyboard microprocessor.

The surface level is typically used when writing utility programs, operating outside of the main application programs. The intermediate level is the level to use when writing the application programs. This level is impervious to key assignments frequently done by users at the DOS level which could impair the integrity of the input controls within your program. The deep level is seldom used, but some special programs necessitate input control at this level; an example of such an application might be a program for the redefinition of the QUERTY keyboard into the DVORAK keyboard which would be operational even within the application word processing programs.

The subroutines within this group may be classified to two groups. The first group consists of subroutines, controlling the state of the keyboard, as the state of the NUmLock, CapsLock, and Scroll Lock keys. These keys should be reset into the preferred states prior to passing control into the main routines of the program. The second group of subroutines in this category consists of the inkey types of subroutines; main subroutines of this group are the a_key, b_key, and key subroutines. These subroutines operate at the A and B level of interrupts. The subroutines of choice are the b_key and the key. One of the important decisions within this context is whether to use the BIOS interrupt 0h/16h for the control of the standard keyboard, or the BIOS interrupt 10h/16h for the control of the extended keyboard. The main advantage of using the extended keyboard is that you may use the function keys 11 and 12 and assign different function to the gray Insert, Delete, Home, End, Page Up, Page Down, to the arrow cluster of keys, and to the corresponding keys on the numerical keypad. However there is a price to pay in the incompatibility of your program with the older keyboards.

 

 

DISKS AND DISK FILES

 

Subroutines in this group can determine how many drives are installed on the system, can find the location of home directory, change a drive, describe drive in terms of size and used and free disk space, determine a path within a current directory, change a directory, remove directory, create a new directory, display a directory, delete files, and more. These are the necessary tools for development of larger programming projects, consisting of several programs working in concert.

 

 

COORDINATION OF PROGRAM EXECUTION

 

The distinct separate group are subroutines for control of the program execution, facilitating integration of programs and their coordination. The libraries contain subroutines for the program execution control via the environment tokens, switches located on the continuation of the program invoking command, subroutines for catenation of programs, and more.

 

 

CONTROL OF LASER PRINTERS

 

The Hewlett-Packard Printer Control Language is gaining wide acceptance as a standard for control of the printed output. The description of subroutines in the Cruise LaserJet Library is used to demonstrate low level control of the appearance of the output generated by the Laser printers.

 

 

GETTING STARTED

 

The subroutines and functions in the Cruise Fortran Libraries were named and structured as to become permanent additions to the general programming statements; written for computers using INTEL's iAPX 88 / 186 / 286 / 386 / 486 microprocessors and Microsoft DOS operating system, the subroutines were selected with stress on clarity of presentation, general utility, and anticipated frequency of use. Operations already implemented by the Microsoft Fortran compiler were not duplicated; some of them were extended or simplified. Most routines were written in assembly language, but higher languages, especially C, were used whenever appropriate.

The subroutines were originally tailored to the requirements of Microsoft Fortran Version 3.3. Following version of the library was Beta tested on the Version 4.0 and subsequently tuned to the requirements of the Versions 4.1 and 5.1. In most cases you need not worry about version compatibility, however to minimize possible version conflicts, your compiler should be in the compatible mode with the previous version.

After perusing the library subroutines, create a series of miniature Fortran programs and call the subroutines you think you may use in design of your programs. Every subroutine or function described in the following chapters contains a description of its calling conventions and a fragment of the code which surrounds its invoking and allows for demonstration of its essential properties. Subsequently, you may like to begin the development of your own routines; the chapters on the programming in the assembly language and chapters dedicated to interfacing of assembly language subroutines with the Fortran programs provide direction in this respect.

Initially, it is easier to start a programming project by using library subroutines already developed and complement the available subroutines with the custom designed subroutines according to the specific requirements of each design project. Thus, our discussion commences with the description of key routines contained by the Cruise Fortran Libraries.

CHAPTER

2

 

SYSTEM CONFIGURATION

AND ORIENTATION IN TIME

 

Few initial statements of some programs may pertain to inquiry about the computing environment. The determination of the type of the central processor unit may be of importance for certain applications. Sometimes, some parts of the program may run efficiently only on the 386 or 486 machines and may be better bypassed if the program is run on the iAPX 88 or 286 machines. Typical problem, related to the type of the microprocessor the program is running on is associated with the use of the sound routines. The sound may sound quite differently if the program is running on the 386 machine then when running on the PC or XT. In this case, the checking of the type of the central processor unit may be of importance. Often, the program may have special requirements about the presence of device handlers in the config.sys file or the type of microprocessor the computer was build around. Following these initial inquiries, the program may require the retrieval of time, as set within the computer. Some of the routines which can be employed to accomplish these tasks are discussed in this chapter.

 

 

TYPE OF THE CENTRAL PROCESSOR UNIT

 

The CPU subroutine determines the type of the central processor unit the program is running on. The subroutine is called as

 

call cpu(a)

 

The argument should be dimensioned as character a*1 and returns 1 if the central processing unit is Intel's iAPX 88, 86, 188, 186 or NEC V16 V20 processors. The 2 is returned for iAPX 286, 3 for iAPX 386 central processor unit, 4 for the iAPX 486 or higher. A program fragment, calling the subroutine is presented below.

 

 

Listing 2. Fortran Implementation of the CPU Subroutine

------------------------------------------------

character a*1

call cpu(a)

select case(a)

case('1')

write(*,*)'The Central Processing Unit is Intel''s iAPX 8088'

case('2')

write(*,*)'The Central Processing Unit is Intel''s iAPX 286'

case('3')

write(*,*)'The Central Processing Unit is Intel''s iAPX 386'

case('4')

write(*,*)'The Central Processing Unit is Intel''s iAPX 486 or higher'

end select

end

------------------------------------------------

 

 

VERSION OF THE OPERATING SYSTEM

 

The DOS_VER subroutine allows you to determine the version of the operating system. The subroutine is called as

 

call dos_ver(i,j)

 

where i returns the verson number and j the revision number. A program fragment, calling the subroutine is presented below.

 

 

Listing 2. Version of the Operating System

------------------------------------------------

call dos_ver(i,j)

write(*,*)i,j

end

------------------------------------------------

 

 

SYSTEM MEMORY

 

The MEMORY subroutine allows you to determine whether the system has at least the full amount of the 640K memory. The subroutine is called as

 

call memory(ix)

 

and the amount of memory available is returned in the ix parameter. A program fragment, calling the subroutine is presented below.

 

 

Listing 2. System Memory

------------------------------------------------

call memory(ix)

write(*,*)ix

end

------------------------------------------------

 

The subroutine uses the BIOS interrupt 12h which returns the number of contiguous 1k blocks of memory up to 640 K.

 

 

UPPER MEMORY

 

The MEM_UP subroutine determine the available amount of the memory above the 1M. The subroutine is called as

 

call mem_up(ix)

 

and returns the amount of the extended memory installed in the ix parameter. A program fragment, calling the subroutine is presented below.

 

 

Listing 2. System Memory

------------------------------------------------

call mem_up(ix)

write(*,*)ix

end

------------------------------------------------

 

The subroutine uses the BIOS interrupt 88h/15h, returning the number of contiguous 1k blocks of RAM above 1M.

 

 

MEMORY AVAILABLE TO PROGRAM

 

To determine the amount of memory available to the program, you may call the MEM subroutine. The mem subroutine uses the DOS interrupt 48h/21h to allocate memory which, on return, contains in the bx register the maximum number of continuous paragraphs available. The subroutine can be called form a Fortran program as

 

call mem(i)

 

where i is the amount of available memory in paragraphs. The mem subroutine in no way alters the memory contents. Since every paragraph is 16 bytes long, the conversion into bytes is easy to accomplish, as suggested in the following fragment of Fortran code.

 

 

Listing 6. Determining the Anmount of Remaining Memory

------------------------------------------------

call mem(i)

write(*,*)i*16

end

------------------------------------------------

 

 

PRESENCE OF ANSI.SYS DRIVER

 

Sometimes, the programmer may wish to make use of the ansi.sys driver. The ANSI subroutine detects the presence of this driver. The ansi.sys device driver facilitates extended screen and keyboard control by using series of standardized escape sequences. The driver is located in the config.sys file as a statement device=ansi.sys. The subroutine is called as:

 

call ansi(a)

 

The argument should be dimensioned as character a*1. The argument returns the plus sign if the driver is present and the minus sign if the driver was not installed at the time the computer was booted. Example of a Fortran program call is listed below.

 

 

Listing 2. Fortran Implementation of the ANSI Subroutine

------------------------------------------------

character a*1

call ansi(a)

if(a.eq.'+')then

write(*,*)'Ansi.sys detected'

else

write(*,*)'Ansi.sys absent.'

endif

end

------------------------------------------------

 

 

RETRIEVE DATE AND DAY OF THE WEEK

 

The Microsoft Fortran intrinsic function GETDAT retrieves the year, month, and day. The DAY subroutine returns the current date as set in the operating system, including the day of the week. Parameters of the subroutine are month, day of the month, year, and the day of the week. The subroutine is called as

 

call day(im,id,iy,iw)

The month and the day are returned as one or two digit integers, the year is a four digit integer, the day of the week is returned as a one digit integer which indexes Sunday with zero, Monday with 1, etc. An example calling this subroutine from the Fortran program is listed below.

 

 

Listing 2. Retrieving Date and Day of the Week

------------------------------------------------

call day(im,id,iy,iw)

if(iw.eq.3)write(*,*)'Call Maurice.'

end

------------------------------------------------

 

 

DATE COMPOSITION

 

The DAYS subroutine allows to write the system date with months and days spelled out. It also allows to integrate the date into the surrounding text and add the specification of the locale. The subroutine is called as

 

call days(len1,len2,datum)

 

and calculates the relevant lengths within the returned alphanumeric string allowing for writing date without unsightly gaps between months of the varying length. The len1 argument is the length of the datum string up to the end of the year and len2 the length of the datum string up to the end of the day of the week. The datum array is variably dimensioned, the recommended size of the datum array is datum*30. The Fortran calls are presented below.

 

 

Listing 2. Composing the Date

------------------------------------------------

character datum*30

call days(len1,len2,datum)

write(*,'(6h Date:,1x,a)')datum(1:len1)

write(*,'(6h Date:,1x,a,1h.)')datum(1:len2)

write(*,*)datum

write(*,'(a,a)')' Minneapolis, Minnesota, ',datum

end

------------------------------------------------

 

result in date formats (for a certain system date), as tabulated below.

 

Table 2. Date Formats of the Days Subroutine

------------------------------------------------

Date: January 7, 1991

Date: January 7, 1991; Friday.

January 7, 1991; Friday

Minneapolis, Minnesota, January 7, 1991; Friday

 

------------------------------------------------

 

 

MEASURE ELAPSED TIME

 

The CENTIS subroutine computes elapsed time in centiseconds. It is used in benchmarking and comparisons of efficiency of alternative routines. The subroutine returns the elapsed time in centiseconds in its argument. It is called as

 

call centis(itime1)

 

An example of its usage is given below.

 

 

Listing 2. Measuring the Elapsed Time in Centiseconds

------------------------------------------------

call centis(itime1)

... code...

call centis(itime2)

ics=itime2-itime1

write(*,*)ics

------------------------------------------------

 

The SECNDS subroutine computes elapsed time in seconds. It is used for timing execution of various tasks, such as iteration routines or lengthy calculations, it indicates that something is happening, that the computer didn't just go down, but is working on some task which takes time, and it tells how much time the task takes. This timing also helps the user learn to estimate the time it will take to execute some other lengthy task, by providing experience with timing of similar tasks. An example of its usage follows.

 

 

Listing 2. Measuring the Elapsed Time in Seconds

------------------------------------------------

logical*1 terminate \.false.\

ics=0

call secnds(it1)

1 write(*,'(1h+,40x,5hTime:,i7)')ics

call iterate(terminate)

if(terminate)goto 9

call secnds(it2)

ics=it2-it1

goto 1

9 write(*,*)'Iteration terminated.'

------------------------------------------------

 

 

DELAY PROGRAM CONTINUATION

 

The DELAYS subroutine delays execution of the next program statement by the number of deciseconds specified by its argument. The subroutine is called as

 

call delays(idecs)

 

The valid time range is 1-600 deciseconds, i.e., from .1 second to one minute. The values of the argument and their corresponding time values are presented below.

 

Table 2. Translations of Seconds and Minutes to Centiseconds

------------------------------------------------

 

.5 sec

1.0 sec

3.0 sec

10.0 sec

1.0 min

5 deciseconds

10 deciseconds

30 deciseconds

100 deciseconds

600 deciseconds

 

 

An example of the Fortran call follows.

 

 

Listing 2. Delaying Execution of the Next Program Statement by Specified Number of Deciseconds

------------------------------------------------

call cls

write(*,*)'Hello'

call delays(30)

call cls

------------------------------------------------

 

The delays subroutine uses the BIOS interrupt 86h/15h which is available only on the i286 class of computers or higher. An alternative form of this subroutine is the subroutine delay which performs the same task by calling the computer timer directly. The argument of the delay subroutine is calibrated in centiseconds; thus the equivalent of the call of the delays subroutine as call delays(30) would be the call of the delay subroutine as call delay(300).

CHAPTER

3

 

 

THE KEYBOARD

 

The subroutines for the control of the keyboard help to determine the initial status of the keyboard, i.e., are the capitals locked in or what is the status of the numerical keypad. They also allow for redefinition of key assignments. The main subroutines of this group secure input of information into the program. The input can be sceured at the level of DOS interrupts, at the level of BIOS interrupts, or by directly addressing the keyboard hardware ports. It is quite important to decide which level of input from the keyboard to use for specific features of the program. This is the central issue of discussion in this chapter.

 

 

TYPE OF KEYBOARD

 

The extended keyboard was introduced in 1987 and added the keypad keys, several new 'grey' keys and Function 11 and Function 12 keys. In BIOS, new interrupt 10h/16h was added, allowing to acces keystrokes not recognized by the old interrupt 0h/16h. The extended keyboard is available only on the i286 class of computers or higher. To recognize the presence of the extended keyboard, the keyboard subroutine writes a test pattern to the keyboard buffer and reads it back. The subroutine is called as

 

call keyboard(a)

 

The keyboard's subroutine single argument returns a plus sign if the extended keyboard was detected, the minus sign if the extended keyboard is not attached to the computer. An example of a Fortran call is

 

Listing 3. Type of Keyboard

character*1 a

call keyboard(a)

write(*,*)a

end

 

and the subroutine is listed below.

 

 

Listing 3. Type of Keyboard Source Listing

------------------------------------------------

;T H E KEYBOARD S U B R O U T I N E

;call keyboard(a) ;a typical Fortran call

public keyboard

.model large

.stack 64

.code

keyboard proc far

;Save base

push bp

mov bp,sp

;Save registers

push ax

push bx

push cx

push dx

mov bx,[bp+6] ;the last argument

;Write Test Pattern to Keyboard Buffer

mov cx,0ffffh

mov ah,05h

int 16h

mov cx,16 ;set loop counter

do: mov ah,10h

int 16h

cmp ax,0ffffh

je ext

loop do

jmp old

ext: mov al,'+'

mov [bx],al

jmp adieu

old: mov al,'-'

mov [bx],al

adieu: pop dx

pop cx

pop bx

pop ax

pop bp

ret 4

keyboard endp

end

 

 

SENSING THE STATE OF CAPS LOCK AND

NUM LOCK KEYS

 

The C_LOCK subroutine acknowledges whether the CapsLock is engaged. Checking for the engaged CapsLock key before requesting case sensitive input is a considerate practice. The subroutine is called as

 

call c_lock(x)

 

where the argument x is declared as character*1. The plus character is returned if the capital letters are locked in, otherwise the minus is reported upon the return of the subroutine to the calling program. A fragment of the program, using this subroutine is presented below.

 

 

Listing 3. Sensing whether the CapsLock Key is Engaged

------------------------------------------------

character*1 x

call c_lock(x)

if(x.eq.'+')write(*,*)'Please reset the CapsLock key'

end

------------------------------------------------

 

The N_LOCK subroutine reports whether the NumLock key is engaged. Most computers boot with the keypad locked in the numerical mode. Applications which use extensively the Insert key often have problems with users pressing the Insert key on the keypad locked in the numerical mode and getting zero inserted instead. Similar problems may be encountered when using the End, Home, Delete, or Page Up and Page Down keys for the program execution control. The subroutine is called as

 

call n_lock(x)

 

with the argument x declared as character*1, returning the plus sign if the NumLock key is engaged and the minus sigh otherwise. A program fragment forcing the user to reset the NumLock key is presented below.

 

 

Listing 3. Sensing whether the NumLock Key is Engaged

------------------------------------------------

character*1 x

1 call n_lock(x)

if(x.eq.'+')then

call writes('n',23,40,'n','Please reset the NumLock key')

goto 1

endif

------------------------------------------------

 

Both the c_lock and n_lock subroutine help with the development of the user interfaces sections of the application programs.

 

 

UNLOCKING CAPS AND KEYPAD KEYS

 

The LOCK subroutine gives you direct control over the keyboard Lock keys. Calling the subroutine unlocks the NumLock, CapsLocks, and the Scroll Lock keys. The subroutine is called as

 

call locks

 

and has no arguments. A fragment of the program, using this subroutine is presented below.

 

 

Listing 3. Unlocking the Lock Keys on the Keyboard

------------------------------------------------

call locks

end

------------------------------------------------

 

 

REDEFINE KEYBOARD KEY ASSIGNMENTS

 

The KEY_DEF subroutine facilitates the assignment of alternate characters, text, or commands to virtually any keyboard key from within the Fortran program. The key_def subroutine can be called in a declarative mode or as a query. The declarative mode, signified by an exclamation mark, appends the carriage return after the text string so that the command gets executed. The query mode does not append the carriage return, so that further specifications may be added to the command before its execution. An example of the declarative mode, defining the ALT D key as a MS-DOS directory command, is

call key_def('0;32,dir/p!')

An example of a query is presented below

call key_def('0;46,copy *.* ?')

assigning the copy all command to the ALT C key. The argument of the key_def subroutine must always be terminated with either a question mark or with an exclamation mark, the scan code must be separated from the text by a comma, and the whole argument must be enclosed in single quotes. An example of a Fortran program, using the key_def subroutine, is listed below.

 

 

Listing 3. Assigning Text or Commands to Keyboard Keys

------------------------------------------------

call key_def('0;30,Hi there!') ! Alt A

call key_def('0;46,Copy ?') ! Alt C

stop 'Keys defined.'

end

------------------------------------------------

 

 

INPUT AT THE LEVEL OF DOS INTERRUPTS

 

Subroutine A_KEY accepts input of a single character from the keyboard without depression of the enter key. The keyboard input is via MS-DOS call 7h/21h, which does not echo and ignores the control c sequence. This routine is called as

 

call a_key(a,'*')

 

The first argument has to be dimensioned as character*1. The second argument, if entered as '+', translates of the secured characters into the uppercase; if entered as '-', translates the character in the first argument to lowercase. Any other character causes the subroutine to ignore the uppercase or lowercase translation options. A program fragment, illustrating the use of the a_key subroutine is presented below.

 

 

Listing 3. The A_KEY Subroutine for Keyboard Input

------------------------------------------------

character*1 a

write(*,*)'Translate input into lowercase'

call a_key(a,'-')

write(*,*)a

write(*,*)'Translate input into uppercase'

call a_key(a,'+')

write(*,*)a

write(*,*)'Input without translation of case'

call a_key(a,'0')

write(*,*)a

end

------------------------------------------------

 

The a_key subroutine is capable of reading the keyboard key assignments made by the key_def subroutine; it also can recognize the F11 and F12 function keys.

 

 

INPUT AT THE LEVEL OF BIOS INTERRUPTS

 

Subroutine B_KEY accepts input of a single character from the keyboard without depression of the enter key. Typically, it is used used to secure input from the special keys as arrows, page up, page down, home, end, etc. The keyboard input is via BIOS call 0h/16h, which does not echo, ignores the control c sequence and checks for the control break sequence only if the DOS command break on is stored in the environment. Also, the BIOS 0h/16h interrupt does not recognize the F11 and F12 keys. This routine is called as

 

character a*1

call b_key(a,ix,'*')

 

where the first argument returns the character typed at the keyboard and the second argument returns the key scan code. The third argument, if specified as '+', translates the input characters into uppercase, if specified as '-', into the lowercase. Any other character in the third argument causes the subroutine to ingnore the case translation options.

Subroutine X_KEY is the upgraded b_key subroutine, calling the 10h/16h interrupt and making possible to secure input from the enhanced keyboard, including the input from the F11 and F12 Keys and over 20 new control and alternate keys combinations.

A tour de force is the subroutine XB_KEY which tests the keyboard and selects the appropriate call, either via the 0h/16h, or the 10h/16h interrupts. Using this subroutine you may write programs using all keys of the extended keyboard which are also compatible with the old PC and XT machines. The new keys should be used as accelerator keys only. In case the program is run on computer with an old keyboard, the program should provide some roundabout path to access all its features. The penalty for using the xb_key subroutine is the increase in size compared to either b_key or the x_key subroutines, and inability to type ahead, since the input buffer must be flushed after the keyboard test. Since this subroutine is typically not used for the input of text, but only to secure input at the decision points, this typically presents no problem. Where the compatibility is at premium, the xb_key subroutine is the subroutine of choice.

An example of the use of the x_key subroutine is listed below

 

 

Listing 3. The X_KEY Subroutine for Keyboard Input

------------------------------------------------

character*1 a

1 write(*,*)'Translate input into lowercase'

call x_key(a,ix,'-')

write(*,*)a,ix

write(*,*)'Translate input into uppercase'

call x_key(a,ix,'+')

write(*,*)a,ix

write(*,*)'Input without translation of case'

call x_key(a,ix,'*') !terminates on ctrl break if break on

write(*,*)a,ix

write(*,*)'Press F12'

call x_key(a,ix,'*')

write(*,*)a,ix

write(*,*)'Press CTRL U'

call x_key(a,ix,'*')

write(*,*)ichar(a),ix

write(*,*)'Press ALT U'

call x_key(a,ix,'*')

write(*,*)ichar(a),ix

write(*,*)'Press GRAY INS'

call x_key(a,ix,'*')

write(*,*)ichar(a),ix

write(*,*)'Press INS on the Keypad'

call x_key(a,ix,'*')

write(*,*)ichar(a),ix

write(*,*)'Lock Keyboard and Press INS on the Keypad'

call x_key(a,ix,'*')

write(*,*)ichar(a),ix

goto 1

end

------------------------------------------------

 

 

and its source code listing is

 

 

Listing 3. Source Code of the X_KEY Subroutine

------------------------------------------------

 

;T H E X_KEY S U B R O U T I N E

;call x_key(a,ix,'+') ;a typical Fortran call

public x_key

.model large

.stack 64

.code

x_key proc far

;Save base

push bp

mov bp,sp

;Save registers

push ax

push bx

push cx

push dx

mov bx,[bp+6] ;the last argument

mov cl,[bx]

;Input character and scan code

mov ah,10h ;extended keyboard

int 16h

;Conversions to upper or lower case

cmp cl,'+'

je uppercase

cmp cl,'-'

je lowercase

jmp deposit

uppercase label near

cmp al,'a'

jb deposit

cmp al,'z'

ja deposit

and al,01011111b ;mask out the fifth bit

jmp deposit

lowercase label near

cmp al,'A'

jb deposit

cmp al,'Z'

ja deposit

or al,00100000b ;convert to lowercase

deposit label near

mov bx,[bp+14] ;first argument address

mov [bx],al ;ASCII char out

mov si,[bp+10] ;address of ix

mov [si],ah ;scan code out

;Return

pop dx

pop cx

pop bx

pop ax

pop bp

ret 12

x_key endp

end

------------------------------------------------

 

 

THE MAPPED KEYBOARD INPUT

 

The KEY subroutine secures input of both upper and lower parts of the ASCII character set by mapping selected characters from the upper ASCII set into the alternate keys of the keyboard. The chart of the mapped keys can be overlayed over the current screen display by pressing a selectable key on the keyboard, typically the F1 or F2 key. The display of the chart is context sensitive. The selection of the mapped characters and their assignments are quite general and confirm to the requirements of most programming designs. The alt 0, alt 1 and alt 2 keys add the capability to use the zero, 1/2, and 2 powers. The Greek characters are mapped into their corresponding Latin character alt keys and the scientific notation characters between ASCII 224 and ASCII 255 are mnemonically assigned to alternate keys of the keyboard, as, e.g., the infinity sign being mapped to alt i, and the square root sign into the alt r. The key subroutine is called as

 

call key(a,ix,'*',map)

 

The first three arguments of the key subroutine are the same as of the b_key or b_keys subroutines. The first argument returns the character typed, the second argument returns the scan code. The scan codes returned by the BIOS interrupt 0h/16h are listed in the last chapter, describing the programming environment utilities, including the relevant tables. The third argument, if set to '+", translates the input into uppercase, if set to '-', into the lowercase. The last argument defines the key to be used as the 'help' key displaying the chart of the mapped characters. A program fragment, using the key subroutine is listed below.

 

 

Listing 3. The KEY Subroutine for Keyboard Input

------------------------------------------------

character a*1

call key(a,ix,'*',59) !The help key is F1

if(ix.eq.1)stop

call writes('r',7,7,'n',a)

---------------------------------------------

 

The Key subroutine is typically called from the Reads subroutine.

 

 

SECURE RESPONSE TO A QUERY

 

The QUERY subroutine saves coding of several Fortran statements frequently used to obtain keyboard input, check for input errors, and branch according to the value of the input character. The argument field of the query subroutine consists of two parts. The first part contains an integer variable returning the ordinal position of the selected character located in the second argument field. The second part contains list of tokens to be matched. All tokens must be in lowercase. The Fortran call of this subroutine is as, e.g.,

call query(ians,'a,b')

Following the call, a match is attempted between a keyboard reply (no carriage return, inkey type) and the argument list. If no match is found, the query routine expects additional input. Any uppercase input is translated into lowercase. Single digit numbers such as 0,1,2,3,4,5,6,7,8,9 can be used. To specify keys with no corresponding keyboard characters, associate the BIOS keyboard code with an ASCII character code; thus, e.g., the function key F1 with the BIOS keyboard code of 59 translates into a semicolon. The query subroutine also checks for the escape key. If the escape key is depressed, the ians argument will be returned as equal to zero. An example of a Fortran program using this subroutine is listed below

 

 

Listing 3. The QUERY Subroutine for Selection from a List of Alternatives

------------------------------------------------

write(*,*)'Select alternative A B or C'

call query(ians,'a,b,c')

select case(ians)

case(0)

stop

case(1)

write(*,*)'The first decision point'

case(2)

write(*,*)'The second decision point'

case(3)

write(*,*)'The third decision point'

end select

------------------------------------------------

 

 

The source code for the Query subroutine is

 

 

Listing 3. Source Code of the QUERY Subroutine

------------------------------------------------

subroutine query(iorder,a)

character *(*)a,x*1

max=length(a)

1 call b_key(x,iterm,'-')

if(iterm.eq.1)then !Escape

iorder=0

return

endif

iorder=0

do i=1,max

if(a(i:i).ne.',')iorder=iorder+1

if(a(i:i).eq.x)return

if(ichar(x).eq.0.and.ichar(a(i:i)).eq.iterm)retu rn

end do

call tone 0

goto 1

end

------------------------------------------------

CHAPTER

4

 

 

SCREEN DISPLAYS

 

The subroutines controlling the screen displays are the principal tools of the program development. In this chapter we are going to discuss subroutines for displaying information on the computer's screen, reading information displayed on the screen, and designing screen displays. The characters, used in screen displays and their corresponding codes are listed in Tables on the end of the book. Aside of the regular letters of the alphabet, they also include special characters, Greek characters, characters for drawing lines and borders and characters for creation of special graphic effects.

Each character displayed in the video buffer contains two bytes of information. The ASCII value of the character and the character's display attribute. The attribute byte determines whether a character is highlighted, blinking, underlined, or in reverse video. The subroutines discussed frequently provide for specification of the attribute byte of the displayed characters in the first parameter of their arguments.

The key procedures for programming screen displays are the window, reads, readn, writes, writen subroutines. They are complemented by the subroutines for framing screen display areas, creation of screen background textures, and by numerous auxiliary subroutines.

 

 

CLEAR SCREEN

 

The CLS subroutine clears the screen by scrolling it up and positioning the cursor to the upper left corner. The Fortran call is

call cls

The cls subroutine scrolls the screen and repositions the cursor by using the BIOS interrupt 10h. This routine is the Fortran callable counterpart of the MS-DOS keyboard callable utility cls.

A variation of the cls subroutine is the subroutine SCD. The SCreen Down subroutine is functionally equivalent to the CLS subroutine. However, it scrolls the screen down and positions the cursor to the lower left corner of the screen. The subsequent screen displays will be scrolled from the bottom to the top of the screen. The subroutine is called from a the Fortran program as

 

call scd

 

and has no arguments.

 

 

REVERSE SCREEN

 

The REVERS subroutine changes the surface of a video screen into the reverse video and back to the normal state. This subroutine has two parameters. The plus sign to change screen to a reverse video and the minus sign to obverse screen to the normal state. Example of the Fortran call is

call revers('+')

Sample usage is presented in Listing 4..

 

 

Listing 4. Reverse Video Screen

------------------------------------------------

character*1 a

write(*,*)' Revers <+/->'

read(*,'(a)')a

call cls

call revers(a)

write(*,*)' Hello world'

call tab

call scd

write(*,'(24(1x,77(1h*)/))')

call tab

------------------------------------------------

 

 

BLINKING MESSAGE

 

The BLINK subroutine makes the text blink. The text should be terminated with another call blink statement, this time with the minus argument. To remove the blinking text, overwrite it with blanks. The Fortran call is

call blink('+')

An example of its use is listed below.

 

 

Listing 4. Blinking Messages

------------------------------------------------

call blink('+')

write(*,*)' Hello world'

call blink('-')

------------------------------------------------

 

The Blink subroutine operates on the level of DOS interrupts.

 

 

INVISIBLE PASSWORD ENTRY

 

The SECRET subroutine turns the screen display off. It can be used in situations when you want to secure input from some peripheral device as, e.g., the keyboard, without echoing the input on the screen. This subroutine is useful when entering a secret password. Another use of this subroutine is in situations when a graphic display fills the whole screen and a response to a query would write over some part of the display.

The +/- [on/off] switch constitutes the only parameter. The plus sign turns the screen display off. The minus sign restores the normal screen display. The cursor is not turned off by this subroutine. To totally blank the screen, you have also to call the cursor('i') subroutine. Example of the Fortran call is

call secret ('+')

An example of the use of this subroutine is listed below.

 

 

Listing 4. Invisible Writing into the Screen

------------------------------------------------

character string*6,mask*6

mask='sesame'

write(*,'(a\)')' Enter password: '

call secret('+')

read(*,'(a)')string

call secret('-')

if(string.eq.mask)then

write(*,*)'Access permitted.'

else

write(*,*)'Access denied.'

endif

------------------------------------------------

 

 

REDEFINE CURSOR SHAPE

 

The CURSOR subroutine, although simple, is indispensable for the modern screen design. To make the cursor take on a different shape when a different type of input is expected, or to make the cursor disappear, is one of the most mundane programming chores. The subroutine is called as, e.g.,

 

call cursor('i')

 

with possible values of the argument summarized in Table 4..

 

Table 4. Definitions of Cursor Shape

[i] invisible

[h] hair

[l] line

[d] dash

[c] cube

[b] block

[s] split

[u] underlined

makes cursor disappear

a thin bottom line

a double line cursor

a heavy line cursor

a small square

a large rectangle

a top-bottom thin line cursor

an exclamation mark shape

 

LOCATE CURSOR

 

The GETCUR subroutine returns the screen coordinates of the current cursor location. The determination of these coordinates is necessary for the flexible screen display control. This subroutine is called from the Fortran program as

 

call getcur(i,j)

 

The integer parameters i and j are the screen row and column cursor coordinates. An example of the use of this subroutine is given below.

 

 

Listing 4. Location of the Cursor

------------------------------------------------

call getcur(i,j)

write(*,'(a,2i3)')' Cursor location:',i,j

call tab

end

------------------------------------------------

 

 

REPOSITION CURSOR

 

The SETCUR subroutine repositions cursor to a new location on the screen. The subroutine is called as

 

call setcur(i,j)

 

The integer parameters i and j are row and column screen coordinates where the cursor is to be repositioned. The setcur subroutine is called in an absolute manner as, e.g.,

 

call setcur(12,30)

 

which repositions the cursor to a certain location on the screen. The relative call to this subroutine has to be preceded by the call to the getcur subroutine, as shown in the example below.

 

 

Listing 4. Location of the Cursor

------------------------------------------------

call getcur(i,j)

call setcur(i+3,j+7)

write(*,'(a,2i3)')' The Offset of this Message is',i+3,j+7'

------------------------------------------------

 

 

FRAME A SCREEN DISPLAY AREA

 

The square areas, delineated by single or double line borders, are the building blocks of the modern screen designs. Calling the FRAME subroutine will create a box on the screen, optionally in reverse video. The arguments of the Frame subroutine are similar to those of the Window subroutine and the Frame subroutine should be used whenever possible in lieu of the Windows subroutine, since it carries substantially lower overhead. The use of the Frame subroutine is mandatory in cases of chaining a number of boxes in circular menus which can recur dozens or hundreds of times, since the repeated use of the Windows subroutine will eventually run out of the memory storage. Obviously, the Frame subroutine does not preserve the content of the overlaid area on the screen, as the Window subroutine. Often, the Window and the Frame subroutines can be combined. The Window subroutine is called first to overlay the screen display, and the window is subdivided into the areas dictated by its design by the Frame subroutine.

To position the box on the screen, first, define its appearance by using one of the q r t s arguments, described below, and then define its coordinates by using the standard screen grid indexed from 0 to 24 by its rows and from 0 to 79 by its columns. The Fortran call is

 

call frame('x',i,j,k,l)

 

where x determines the character of the border lines and i,j,k,l are the coordinates of the upper left and the lower right corner of the box. The values of the x parameter are 'b' for bold(thick) borders, 'c' for borders combining double and single line border, 'q' for the double line borders, 'r' for the double line border and the reverse video fill, 's' for the single line border, and 't' for the single line border and the reverse video fill. A fragment of a program, calling the frame subroutine, is listed below.

 

 

Listing 4. Framing a Screen Area

------------------------------------------------

call cursor('i')

call cls

call frame('q', 0, 0,23,79)

call frame('r',10,15,12,45)

call frame('s',17, 8,20,60)

call frame('t',17, 8,20,60)

call tab

call setcur(0,0)

------------------------------------------------

 

 

SCREEN BACKGROUND TEXTURES

 

The SHADOW subroutine changes the ordinarily black screen background into mellow textures, well suited to be superimposed on screen windows or for subsequent framing. Three texture types are available. These types are selected by calling the shadow subroutine as shown in the following statements.

 

call shadow('dark',i,j,k,l)

call shadow('medium',i,j,k,l)

call shadow('light',i,j,k,l)

 

where the i,j,k,and l arguments define the screen coordinates of the upper left and bottom right corners of the area to be painted over. The shadow subroutine can well complement the window subroutine or be complemented by the frame subroutine, as shown in the example of Fortran implementation of the window, frame, and shadow subroutines below.

 

 

Listing 4. Framed Screen Areas With Textured Background

------------------------------------------------

call cursor('i')

call window('s',0,0,24,79)

call shadow('dark',1,1,23,78)

call tab

call shadow('medium',15,15,20,60)

call frame('d',15,15,20,60)

call tab

call shadow('light',17,17,19,70)

call frame('d',17,17,19,70)

call tab

call shut

call cls

call cursor('h')

------------------------------------------------

 

In the above example, notice that the shadow routine painting the suface of the window must be called after the window subroutine and must be dimensioned to cover a smaller area than the window subroutine not to overwrite the borders of the window. On the other hand, the frame subroutine should be either called after the shadow subroutine, or dimensioned with coordinates not overlapping with the coordinates of the shadow subroutine.

 

 

DEFINE ATTRIBUTES OF THE SCROLLED SCREEN

 

The SCROLL subroutine imposes a new attribute on selected areas of the screen surface. In some respects, this operation opens a "window" on the video screen. However, as contrasted with the window subroutine, the screen area painted over is not saved and cannot be subsequently restored. It is also possible to use the scroll subroutine just for clearing of selected screen areas without changing the screen surface attribute. The first parameter of the scroll subroutine is the attribute to be imposed on the scrolled screen area. Three attributes are supported. Reverse [r], underlined [u] and normal [n]. The next four parameters are the left upper row and column [i,j] and the right lower row and column [k,l] coordinates. The example of Fortran call is

call scroll('r',0,0,15,79)

A sample Fortran program, demonstrating the properties of the scroll subroutine is presented below.

 

 

Listing 4. Scrolling the Screen Areas

------------------------------------------------

character*1 x

write(*,*)'Define the Scroll Attribute <r,u,n>'

read(*,'(a)')x

write(*,*)'Coordinates of the Upper Left and Lower Right Corners:'

read(*,*)i,j,k,l

call scroll(x,i,j,k,l)

------------------------------------------------

 

 

COVER PAGE DISPLAY

 

The COVER subroutine composes full screen displays using characters from the Cruise Font Library. This subroutine has two arguments. The first argument is the character to use for the writing letters, selected from the font library. Letters in the font library were constructed by using character 219. Using another fill characters, as # 221 or 240 can improve the appearance of the front page of your program. The second argument of the subroutine is the file name, containing your front page display. The subroutine is called, as, e.g.,

 

call cover(x,'front_disp.fnt')

 

and can be preceded by calls to screen formatting subroutines such as the cls, shadow or frame subroutines. An example of a program, demonstrating how the selection of the fill character can change the appearance of the font is listed below.

 

 

Listing 4. Cover Page Displays

------------------------------------------------

character x*1

x=char(219)

call cls

call frame('s',0,0,23,79)

call cover(x,'front_disp.fnt')

call tab

------------------------------------------------

 

 

PAGINATION OF SCREEN DISPLAYS

 

 

Subroutine TAB stops the display until a key is depressed. It also checks whether the key depressed generates an extended keyboard code, preceded by the ASCII null character. If detected, the leading null character is deleted. Thus, e.g., pressing the space bar or the down arrow results in the same behavior of the program. The tab routine can be called as

 

call tab

 

Functionally similar is the VTAB subroutine. However, the message "Press a key to continue," is displayed in the lower right corner of the screen when this subroutine is invoked. The vtab subroutine is called as

call vtab

Subroutine PAGES stops the display until a key is depressed. The right-adjusted message is displayed in the lower right corner of the screen. When any key is touched, the execution of the program continues. This routine can be called as

call pages('message$,iterm)

where the iterm parameter returns the termination code of the keyboard input. The termination code allows to determine whether, e.g., the input was terminated by the carriage return, or by the insert key, which may be of importance in some programming contexts. However, the most important use of the iterm parameter is to detect the use of the escape key. Illustration of the use of the subroutines for pagination of screen displays is presented below.

 

 

Listing 4. Pagination of Screen Displays

------------------------------------------------

1 call cls

write(*,*)'Hello'

call tab

write(*,*)'Hello world'

call vtab

call pages('Turn page$',iterm)

if(iterm.eq.1)stop

goto 1

end

------------------------------------------------

 

 

WINDOWS

 

The WINDOW subroutine can simultaneously open multiple overlapping windows on the screen surface. The window subroutine has five arguments. The first argument describes the surface of the window and the window borders. The possible values of this argument are 'q r s t u v'. If specified as 'q', the window is opened with the normal surface bordered by the double line frame. This is the default switch and entering any other character aside of the q r s t u v group triggers this switch. This permits renaming the q switch as n, a better mnemonics for 'normal'. If specified as 'r', the window surface is painted as the reverse video with double borders. The 's' switch defines the window as having the normal surface and single line borders. The 't' switch opens the window in reverse video, surrounded by a frame consisting of single lines. The 'u' switch opens a window with the normal surface and no borders and the 'v' switch, also deleting the borders, opens the window in reverse video. In most situations, the simplest and aesthetically pleasing displays are achieved by using the s or v switches.

The following arguments specify the coordinates of upper left and lower right corners of the window. The standard screen grid is used for the description of the window location. The grid has the origin 0,0 in the upper left screen corner. The screen has 24 lines and 80 columns, so the right lower corner of the screen has the 24,79 row, column coordinates. The window coordinates may be any within these limits. The Fortran call to the window subroutine is

 

call window('x',i,j,k,l)

 

To close the window, call the SHUT subroutine as

 

call shut

 

The SHUT subroutine has no arguments and should be called as many times as the window subroutine, to close the windows opened and to restore the screen to its original state. Both the window and the shut subroutines use the direct screen writes, bypassing both the DOS and the BIOS interrupts when saving and restoring the screen surface. They are fast and permit for about six full screens to overlap. An example of the Fortran implementation follows. It demonstrates opening of several stacked windows preserving the information previously stored on the screen, originally filled with dots. Answering in the affirmative to the query whether to close the window removes the previously opened windows in the reverse order, restoring their original surfaces, and, finally, uncovering the intact original screen surface.

 

 

Listing 4. The Window and Shut Subroutines

------------------------------------------------

character x*1

call cursor('i')

call cls

write(*,'(2000(1h.))')

n=0

1 n=n+1

write(*,*)

call b_pen(22,0,' $')

call b_pen(22,0,'Enter window attribute <q,r,s,t> and coordinates$')

read(*,*,err=9)x,i,j,k,l

call window(x,i,j,k,l)

call b_pen(i+1,j+1,'Hello$')

call b_pen(24,50,'Close window? <y,n>$')

call query(ians,'y,n')

if(ians.eq.1)goto 3

goto 1

3 n=n-1

do i=1,n

call shut

call tab

end do

9 call cls

call cursor('h')

end

------------------------------------------------

 

 

READ NUMBERS FROM SCREEN

 

The READN (READ Number) subroutine reads a number from a screen or a screen window and translates the number from a character representation into an internal computer representation of a number. The readn subroutine is called as

 

call readn('x',i,j,format,an,iterm,'0,1,82') !BREAK,ESCAPE,INSERT

 

where 'x' defines the text attributes of a number, as it is read from the screen. The attributes are coded as normal ('n'), reverse video ('r'), blinking ('b'), highlighted ('h'), attention getting, i.e. highlighted and blinking ('a'), underlined ('u') and invisible ('i').

The i and j are the offset coordinates of the screen location the number is going to be read from. The coordinates use the standard screen row, column grid with origin located at 0,0 and lower right corner located at 24,79.

The fourth parameter defines the format of the number. If set to 0.0, it will cause the number to be read in an inkey fashion, securing input of single digit numbers without having to depress the termination key. Formats where the period is followed by zero will read the integer numbers. However, the read number is always encoded as an floating point number. The valid format is any two number combination, separated by a period, within the 0.0 and 9.9 interval. However, only integer numbers smaller than 999,999 (formatted as 6.0) and floating point numbers smaller than 9999.999 (formatted as 4.3) are encoded reliably. Outside of these limits, the floating point numbers may be corrupted past the third decimal place and the integer numbers greater than one million will be unreliable. The encoded number is returned in the last but one argument, always as a floating point number.

The iterm parameter returns the scan code of the key the input was terminated with. If the iterm parameter is returned is equal to -1, a blank string was read. The blank input string typically indicates a missing value. The subroutine checks for the termination codes, listed following the iterm parameter. At least one termination code (e.g., '0' for the CTRL BREAK) must be listed. Up to 64 termination codes can be listed. The readn subroutine recognizes the backspace and delete keys. A program fragment, using the readn subroutine is listed below.

 

 

Listing 4. Reading Numbers from the Screen

------------------------------------------------

call cursor('i')

call cls

an=0.0

1 call writen('r',20,0,10.3,an)

call readn('r',10,15,5.3,an,iterm,'0,1,82') !Break,Escape,Insert

if(iterm.eq. 0)goto 9

if(iterm.eq. 1)goto 9

if(iterm.eq.82)goto 9

goto 1

9 call cursor('h')

end

------------------------------------------------

 

 

The source code listing for the readn subroutine is

 

 

Listing 4. Source Code for the Readn Subroutine

------------------------------------------------

subroutine readn(a,i,j,format,an,iterm,list)

integer*2 itoken(64)

character s*20,z*1,x*1,a*1,slip*3,list *(*)

logical*1 floating,integer,inkey

x=a

if(a.eq.'a')x='n'

if(a.eq.'b')x='b'

if(a.eq.'c')x='r'

if(a.eq.'d')x='h'

if(a.eq.'e')x='a'

if(a.eq.'f')x='u'

if(a.eq.'g')x='i'

2 inkey=.false.

max=format

maxx=anint((format-max)*10.0)

if(max+maxx.gt.20)then

pause 'Format error in CS subroutine readn'

return

endif

if(format.eq.0.0)then

inkey=.true.

max=1

maxx=0

end if

"===DECODE LIST

call blank(slip)

ix=0

ntokens=1

do is=1,len_trim(list)

if(list(is:is).eq.',')then

read(slip,'(i3)')itoken(ntokens)

ix=0

ntokens=ntokens+1

call blank(slip)

cycle

end if

ix=ix+1

slip(ix:ix)=list(is:is)

end do

read(slip,'(i3)')itoken(ntokens)

an=0

call blank(s)

index=1

iterm=0

1 call b_key(z,iterm,'*')

do is=1,ntokens

if(iterm.eq.itoken(is))return

end do

if(index.eq.1.and.iterm.eq.28)then !Missing value

iterm=-1

return

endif

if(iterm.eq.28)goto 5 !CR

if(iterm.eq.14)goto 4 !BS

if(z.eq.'-'.or.z.eq.'+')then

"max=max+1

goto 3

end if

if(z.eq.'.')then

max=max+maxx+1

goto 3

end if

if(a.ge.'a'.and.a.le.'g')goto 3 !Alphanumeric mode

if(z.lt.'0'.or.z.gt.'9')then

call tone 0

goto 1

endif

"_____Echo character

3 index=index+1

if(index.gt.max+1)then

call tone 0

index=index-1

goto 1

endif

call writes(x,i,j,'x',z)

j=j+1

s(index:index)=z

"_____Inkey type

if(inkey)then

iterm=28

goto 5

endif

4 if(iterm.eq.14)then !Backspace

if(index.lt.2)then

call tone 0

index=1

goto 1

endif

index=index-1

j=j-1

if(s(index+1:index+1).eq.'-')max=max-1

if(s(index+1:index+1).eq.'.')max=max-maxx-1

s(index+1:index+1)=1h

call pen(i,j,' $')

goto 1

endif

goto 1

"_____TRANSLATE STRING TO NUMBER

5 floating=.false.

integer=.true.

do ix=1,length(s)

if(s(ix:ix).eq.'.')then

floating=.true.

integer=.false.

end if

end do

if(integer)then

read(s,'(i10)',err=9)num

an=num

elseif(floating)then

read(s,'(f15.2)',err=9)an

endif

return

9 call tone 0 !Internal error read

pause 'Internal error read in CS subroutine readn'

end

 

------------------------------------------------

 

 

WRITE NUMBERS TO SCREEN

 

The WRITEN (WRITE Number) subroutine translates a number in an internal computer representation into a character string. The written subroutine is called as

 

call writen('x',i,j,format,an)

 

The first parameter defines the text attributes of a number, the next two parameters are the screen coordinates, the fourth parameter is the format of the number and the last parameter is the number to be converted into a string and written to any place on screen. An example of an call to the written subroutine is

 

 

Listing 4. Writing Numbers to Screen

-----------------------------------------------

------------------------------------------------

 

"an=99999.99 !Max number

call cls

an=12345.67

call writen('R',6,10,14.2,an)

call tab

end

 

The text attributes are [n]ormal, [r]everse video, [b]linking, [h]ighlighted, [a]ttention getting (highlighted and blinking), [u]nderlined and [i]nvisible. The attributes are case sensitive. If specified in capital letters, the comma, separating thousands, will be inserted if output number is 1,000 or greater. In this case, make sure that the format is wide enough to accomodate the expanded length of the number.

The screen coordinates may vary between 0 and 24 for rows and 0 and 79 for columns. The format follows the format conventions for a floating point number, however is not preceded by the letter f. Thus, e.g.,

 

call writen(i,j,6.3,an)

 

defines a number with five digits, containing a decimal point with three digits following. The format

 

call writen(i,j,5.0,an)

 

defines an integer up to five digits. Decimal point is not printed. The largest format permitted is 15.5, the smallest formats are 2.1 and 1.0. The format must be always a floating point number, for the above example, specifying the format as 5 instead of 5.0 would return an error message.

 

 

The source code for the writen subroutine is listed below.

 

 

Listing 4. Source Code for the Writen Subroutine

-----------------------------------------------

subroutine writen(x,i,j,f,an)

character string*20,out*20,x*1

logical*1 integ,comma

integ=.false.

comma=.false.

if(ichar(x).gt.64.and.ichar(x).lt.91)comma=.true . !Uppercase=insert commas

nw=0

nf=0

nw=f

nf=amod(f,1.0)*10.0+.5

if(nw.gt.15)goto 9

if(nf.gt. 5)goto 9

if(nf.le.0)integ=.true.

call blank(string)

if(integ)then

in=an

write(string,'(i15)',err=9)in

goto 1

endif

if(nf.eq.1)then

write(string,'(f15.1)',err=9)an

elseif(nf.eq.2)then

write(string,'(f15.2)',err=9)an

elseif(nf.eq.3)then

write(string,'(f15.3)',err=9)an

elseif(nf.eq.4)then

write(string,'(f15.4)',err=9)an

elseif(nf.eq.5)then

write(string,'(f15.5)',err=9)an

endif

1 call blank(out)

if(comma)call thousand(string)

if(nw.lt.1)goto 9

out(1:nw)=string(15-nw+1:15)

out(nw+1:nw+1)=1h$

"_____Write number out

call stylus(x,i,j,out)

return

9 write(*,*)'Error in CS FOR.LIB subroutine writen'

end

------------------------------------------------

 

 

READ TEXT FROM SCREEN

 

The READS (READ String) subroutine reads text from any place on screen and echoes the characters read in a display style defined by their corresponding attribute. The subroutine can be also invoked in an 'inkey' mode. The subroutine is called as

 

call reads('x',i,j,format,string,iterm,'0,1,82') !BREAK,ESCAPE,INSERT

 

where 'x' defines the text attribute, i,j, are screen coordinates followed by the format parameter, a variable dimensioned string, and the iterm parameter which upon return contains information how the string was terminated.

The text attributes can be defined as normal ('n'), reverse video ('r'), blinking ('b'), highlighted ('h'), attention getting, i.e. highlighted and blinking ('a'), underlined ('u') and invisible ('i') attributes.

The i,j parameters define the offset of the display on the standard screen grid with the 0,0 origin in the upper left corner, stretching to the 24,79 coordinate point in the lower right corner of the screen.

The format parameter has to be specified as a floating point number and specifies the maximum length of a string. If this maximum length is exceeded at input, cursor stops advancing and a soft murmur sound sounds. The number following the decimal point (1 to 9) can be used to define the width of the right margin of the string which can be optionally overriden by pressing the tab key on the keyboard. The margin can be up to 9 characters wide. If the format is specified as 0.0, the reads subroutine append the carriage return and functions in an 'inkey' mode.

The subroutine checks for termination codes, listed following the iterm parameter. At least one and up to 64 termination codes may be listed. The termination codes returned by the iterm parameter facilitate development of screen displays and parsing algorithms. If a blank string was entered, the iterm parameter is returned as equal to -1.

The prompt of the reads subroutine is an asterisks. Pressing the grey asterisk key on the keypad displays a chart of extended ASCII characters which may be used as input. A fragment of code, using the reads subroutine is listed below.

 

 

Listing 4. Reading Text from the Screen

------------------------------------------------

character string*50

call cursor('i')

call cls

1 call writes('r',20,0,'l',string)

call reads('r',10,15,5.9,string,iterm,'0,1,82') !Break,Escape,Insert

if(iterm.eq. 0)goto 9

if(iterm.eq. 1)goto 9

if(iterm.eq.82)goto 9

goto 1

9 call cursor('h')

end

------------------------------------------------

 

 

Source code of the reads subroutine is

 

 

Listing 4. Source Code of the Reads Subroutine

------------------------------------------------

subroutine reads(x,i,j,format,s,iterm,list)

integer*2 itoken(64)

character * (*) s

character * (*) list

character x*1,z*1,slip*3

logical*1 inkey

call writes(x,i,j,'x','*')

"===Format decoding

inkey=.false.

if(format.gt.0.0)then

max=format

maxx=anint((format-max)*10.0)

maxxx=max+maxx

if(maxxx.gt.len(s))then

write(*,*)'Format is greater than lenght of the string.'

pause 'Error in CS subroutine reads'

goto 9

endif

elseif(format.eq.0.0)then

inkey=.true.

max=1

elseif(format.lt.0.0)then

pause 'Format error in CS subroutine reads'

goto 9

end if

"===DECODE LIST

call blank(slip)

ix=0

ntokens=1

do is=1,len_trim(list)

if(list(is:is).eq.',')then

read(slip,'(i3)')itoken(ntokens)

ix=0

ntokens=ntokens+1

call blank(slip)

cycle

end if

ix=ix+1

slip(ix:ix)=list(is:is)

end do

read(slip,'(i3)')itoken(ntokens)

iterm=0

index=0

1 call key(z,iterm,'*',55) !HELP KEY IS GREY STAR

do is=1,ntokens

if(iterm.eq.itoken(is))goto 9

end do

if(index.eq.0.and.iterm.eq.28)then !Missing value

iterm=-1

goto 9

endif

if(iterm.eq.28)goto 9 !CR

if(iterm.eq.14)goto 4 !BS

if(iterm.eq.15)then

max=maxxx !TAB

goto 1

end if

"===Check if Characters are ASCII

do is=1,57 !Past space are control codes

if(iterm.eq.is)goto 3

end do

call tone 0

goto 1

"_____Echo character

3 index=index+1

if(index.gt.max)then

call tone 0

index=index-1

goto 1

endif

call writes(x,i,j,'x',z)

j=j+1

s(index:index)=z

"_____Inkey type

if(inkey)then

iterm=28

goto 9

endif

4 if(iterm.eq.14)then !Backspace

if(index.lt.1)then

call tone 0

index=0

goto 1

endif

index=index-1

j=j-1

s(index+1:index+1)=0

call writes('x',i,j,'x',' ')

goto 1

endif

goto 1

9 return

end

------------------------------------------------

 

 

WRITE TEXT TO SCREEN

 

The WRITES (WRITE String) subroutine positions string on any place on the screen. It provides for definition of text attributes and for string formatting. The subroutine is called from a Fortran program as

 

call writes('x',i,j,format,string)

 

where x contains information about text attributes, i,j, are the screen coordinates followed by the format parameter and a variable dimensioned string. The i parameter specifies the row of the screen where the text is to originate.

The text attributes can be defined as normal [n], reverse video [r], blinking [b], highlighted [h], attention getting (blinking and highlighted) [a], underlined [u], and invisible [i].

The format parameter specifies the formatting of the text within the displayed string and can be written as as 'l' for left justified, 'c' for centered, and 'r' for right justified. Any other character specified skips the string formatting routines.

The j parameter, if used together with the 'l' format defines the left margin of the text line. Used in conjunction with the 'c' or 'r' formats, the j parameter moves the origin of the text string toward the left by the specified numbers of columns. In the case of the 'r' format, the j parameter defines the right margin. Together with the 'c' format allows for optically correct centering of the string. A fragment of the code, using the writes subroutine is presented below.

 

 

Listing 4. Writing Text to the Screen

------------------------------------------------

call cursor('i')

j=0

1 call cls

call writes('r',10,j,'n','Hello')

call tab

call writes('r',11,j,'c','Hello')

call tab

call writes('r',12,j,'r','Hello')

call tab

call setcur(22,0)

write(*,*)'Read j Press E to Exit'

read(*,*,err=9)j

goto 1

9 call cursor('h')

end

------------------------------------------------

 

Source code for the writes subroutine is listed as

 

Listing 4. Source Code for the Writes Subroutine

------------------------------------------------

subroutine writes(x,i,jj,f,s)

character *(*)s,x*1,f*1

k=len(s)

j=jj

if(f.eq.'r')then

j=79-k-jj

elseif(f.eq.'c')then

j=40-k/2-jj

end if

call scribe(x,i,j,k,s)

end

;T H E S C R I B E S U B R O U T I N E

;call scribe(a,i,j,k,'Message$') ;a typical Fortran call

public scribe

stack segment stack 'stack'

db 64 dup (?)

stack ends

code segment

assume cs:code,ss:stack

scribe proc far

;Save base

push bp

mov bp,sp

;Save registers

push ax

push bx

push cx

push dx

push si

push di

;Get the arguments

mov si,[bp+6] ;message

push si ;save message address

mov di,[bp+14] ;column

mov bx,[bp+18] ;row

mov si,[bp+22] ;attribute

;Define cursor

mov dh,[bx] ;row

mov dl,[di] ;column

;Set cursor

mov ah,2

mov bh,0 ;page

int 10h

;Decode the attribute

mov cl,[si] ;get attribute

and cl,11011111b ;convert to uppercase

xor bx,bx ;zero bx register

cmp cl,'R'

je reverse

cmp cl,'H'

je intensity

cmp cl,'B'

je blink

cmp cl,'U'

je underline

cmp cl,'I'

je invisible

cmp cl,'A'

je attention

jmp normal

reverse label near

mov bl,01110000b

jmp go

intensity label near

mov bl,00001111b

jmp go

blink label near

mov bl,10000111b

jmp go

underline label near

mov bl,00000001b

jmp go

invisible label near

mov bl,00000000b

jmp go

attention label near

mov bl,10001111b

jmp go

normal label near

mov bl,00000111b

;Output the string

go label near

mov si,[bp+10] ;message length

mov cx,[si] ;length

pop si ;restore message address

write label near

lodsb

push cx

xor cx,cx

mov cx,1 ;write only one character

mov ah,9 ;BIOS 9/10h write

int 10h

inc dl ;increment cursor

mov ah,2 ;reset cursor

int 10h

pop cx

loop write

;Return

pop di

pop si

pop dx

pop cx

pop bx

pop ax

pop bp

ret 20

scribe endp

code ends

end

------------------------------------------------

 

 

FILL STRING WITH BLANK SPACES

 

The BLANK subroutine fills a string with blank spaces. It has a single parameter; i.e., the string to be filled with blanks. The string is variable dimensioned. An example of the subroutine call is

 

character string*30

call blank(string)

 

A variation of the blank subroutine is the subroutine PRIME. The prime subroutine fills a string with ASCII characters 255. This character does not echo and on the screen behaves as a blank. The subroutine has two parameters. The first parameter is the length of a string to be filled with ASCII 255; the second parameter is the string itself. The string is variable dimensioned. An example of the subroutine call is

 

character string*30

call prime(23,string)

 

The subroutine facilitates work in situations when it is helpful to differentiate between 'soft' and 'hard' blank space.

 

 

FORMAT STRINGS

 

The string formatting subroutines L_ADJUST, CENTER, and R_ADJUST left adjust text within a string, center text, and right adjust text, respectively. This group of subroutines is complemented by the L_PACK and R_PACK subroutines. While the l_adjust and r_adjust subroutines shift text toward their respective margins, the l_pack and r_pack subroutines shift text toward left or right by the indicated number of spaces. Also, the negative arguments are permitted for the l_pack and r_pack subroutines. In this case these subroutines function as shift right/left statements where the shifted characters overflowing the length of the string are discarded. These subroutines are called as

 

call l_adjust(margin,string)

call center(s)

call r_adjust(string,margin)

 

call l_pack(ishift,string)

call r_pack(string,ishift)

 

The strings in all above subroutines are variable dimensioned. A program fragments, illustrating the use of the string formatting subroutines follow.

 

 

Listing 4. Formatting the Text Strings

------------------------------------------------

character string*20

1 read(*,*)string

read(*,*)margin

call l_adjust(margin,string)

write(*,'(1x,a)')'----+----+----+----+'

write(*,'(1x,a)')string

write(*,*)length(string)

goto 1

end

 

-------------------------

character string*20

1 read(*,*)string

read(*,*)margin

call r_adjust(string,margin)

write(*,'(1x,a)')'----+-- --+----+----+'

write(*,'(1x,a)')string

write(*,*)length(string)

goto 1

end

 

------------------------------------------------

character a*10

a='eldorado'

write(*,*)a

call center(a)

write(*,*)a

write(*,*)'----------'

end

 

------------------------------------------------

character xdrive*80,p_drive*64

1 call cls

write(*,*)'Read string'

read(*,*)p_drive

write(*,*)'Read margin'

read(*,*)margin

xdrive=p_drive//'.NAM'

call l_pack(margin,xdrive)

write(*,'(1x,a)')'----+----+----+----+'

write(*,'(1x,a)')xdrive

write(*,*)length(xdrive)

call tab

goto 1

end

 

------------------------------------------------

character string*20

1 read(*,*)string

read(*,*)margin

call r_pack(string,margin)

write(*,'(1x,a)')'----+----+----+----+'

write(*,'(1x,a)')string

write(*,*)length(string)

goto 1

end

------------------------------------------------

 

 

MEASURE LENGTH OF TEXT WITHIN STRING

 

The LENGTH function is similar, but not equivalent to the Fortran intrinsic fu