╔════════════════════════════════════════╗ ║ ║ ║ A D V A N C E D T O P I C S ║ ║ ══════════════════════════════════ ║ ╚════════════════════════════════════════╝ ┌────────────────────────────────────────┐ │ <shft> F1 = Toggle to regular window │ └────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────────────────┐ │ │ │ If you are reading this document for the first time and │ │ are unfamiliar with the commands for scrolling the text, you │ │ should be aware that <ctrl> ▲ (hold down the <ctrl> key and │ │ push the cursor UP key) will scroll the text UP and <ctrl> ▼ │ │ will scroll the text DOWN. You can also use the PageUp and │ │ PageDown keys to scroll quickly through the document. │ │ │ └──────────────────────────────────────────────────────────────────┘ Table of Contents Line ───────────────── ────── 1. Wordwrap 64 2. Dictionaries 308 3. The INIT file 444 4. Zbex 680 1. Introduction . . . . . . . . 680 2. Zbex programs in files and in other windows 774 3. Properties of Zbex . . . . . . 834 4. Zbex utility programs . . . . . . 921 5. Declaration statements . . . . . 977 6. Assignment statements . . . . . . 1110 7. The run and stop statements . . . . 1214 8. Input and output . . . . . . . 1232 9. Comments . . . . . . . . . 1650 10. Subscripts . . . . . . . . 1669 11. Program control (loop, if, goto) . . . 1758 12. Relations . . . . . . . . . 1943 13. Functions . . . . . . . . . 2084 14. Procedures . . . . . . . . 2752 15. Tables . . . . . . . . . 3000 16. Handling directories . . . . . . 3107 17. Special variables and labels . . . . 3213 18. Instructions for debugging . . . . 3500 19. Conditional compiles and other features . 3731 Advance Topics 20. Controlling the (text) display with putc 3932 21. Graphics . . . . . . . . . 4031 22. MIDI instructions . . . . . . . 4978 ────────────────────────────────────────────────────────────────── ┌─────────────────────┐ │ W O R D W R A P │ └─────────────────────┘ 1.1 The Dmuse editor's normal mode of operation is with wordwrap off. In this mode, the right and left margins are ignored when you type. Furthermore, there are no restrictions on the movement of the cursor; you can put the cursor anywhere in a window and begin typing. 1.2 Most word processors that have a wordwrap feature operate under the following restrictions: (1) Wordwrap is always on. (2) The cursor must always stay within the margins. (3) The cursor cannot move below the end of the text. 1.3 To include a wordwrap feature in an editor that normally operates without wordwrap requires a hybred form of wordwrap, whose behavior can sometimes seem confusing. This is why wordwrap is treated as an advanced topic. 1.4 This point can be illustrated with a simple example. Suppose wordwrap is off, and you move the cursor outside the right margin (remember, you can put the cursor anywhere you want when wordwrap is off). Now you turn wordwrap on and begin to type; how should the cursor behave? This question never comes up with ordinary word processors, because the cursor can never go outside the margins. 1.5 With wordwrap on, the Dmuse editor cursor behaves in the following way: (1) You may move the cursor anywhere in the window (inside or outside the margins) using the cursor keys. (2) When you type with the cursor outside the margins, the cursor moves to the right in the ordinary way. (3) When you type with the cursor inside the margins, the cursor moves to the right until it bumps into the right margin. When this happens, the word that is currently being typed, together with the cursor, jumps to the left margin on the next line. This way, the text inside the margins never crosses a margin boundary. This jumping behavior is what gives the wordwrap feature its name. 1.6 The principal advantange of wordwrap is that it allows you to type text without having to think about typing the Enter key (carriage return) at the end of each line. The Enter key has a a special meaning when wordwrap is on. It is used to mark the end of a paragraph (sometimes called a "hard" carriage return). The Enter key will place a carriage return character (CR) at the point of the cursor and will place the cursor at the left margin on the next line. Any text to the right of the cursor will also be moved to the next line. 1.7 When Dmuse is first installed on your computer, the wordwrap feature is disabled. You must use the F9 command with sub-command "w" to turn the wordwrap feature on. Once this feature is turned on, it will be on every time you run Dmuse until you use the F9 , "w" command to turn the feature off. The command KeyPad * will toggle the current window into and out of wordwrap mode. ────────────────────────────────────────────────────────────────── 2.1 When wordwrap is on, moving the margins has a significant effect on text that is inside the margins. If the space between the margins is compressed (left margin moves right or right margin moves left), it is likely that one or more words will bump the right margin. This will cause the text between the margins to be reformatted so that all encounters with the right margin are again avoided. If the space between the margins is expanded (left margin moves left or right margin moves right), the gap between end of each line of text and the right margin will increase, and there will be cases where an extra word will now fit at the end of a line where formerly it had to go on the next line. This will cause the text between the margins to be reformatted. 2.2 The commands for moving the margins are described in all three of the help topics relating to the editor. We summarize them here. left <shft-alt> ◄ = move left margin to the left (◄──). left <shft-alt> ► = move left margin to the right (──►). right <shft-alt> ◄ = move right margin to the left (◄──). right <shft-alt> ► = move right margin to the right (──►). <ctrl-shft> ◄ = move both margins to the left (◄──). <ctrl-shft> ► = move both margins to the right (──►). 2.3 Since each line in a window has its own left and right margins, the important question is which lines will have their margins changed by these commands. Certainly the line the cursor is on will have its margin(s) changed. By convention (default) all lines in the paragraph containing the current line will also have their margins changed. Paragraphs are set off by (1) a carriage return (CR) character, (2) a blank line, or (3) an indented line of text. You can change the number of paragraphs over which the margin commands operate by pressing <ctrl> F10 , choosing the "m" option and following the instructions. 2.4 If you want to move the margins for a specific group of lines, either within a paragraph or spanning more than one paragraph, the procedure is to use the <shft> ▼ keystroke to create a stream highlight spanning the specific group of lines in question, and then move the margin. Care must be taken when using this technique, since the reformatting operation will sometimes add a line or subtract a line unexpectedly. 2.5 If the lines of a paragraph have different margins, only those lines which are contiguous to the line the cursor is on and whose margins are the same as those of the line the cursor is on will have their margins moved. ────────────────────────────────────────────────────────────────── 3.1 The ability to switch into and out of wordwrap mode, and to reformat paragraphs in wordwrap mode by changing the margins is both a strength and a weakness of the Dmuse editor. It is a strength because it provides you with a powerful tool for reformatting paragraphs. Let us say, for example that you want to reformat the text in this paragraph so that the paragraph is half as wide as it is now. You can do this by placing the cursor somewhere inside this paragraph and using the Left <shft-alt> ► keystroke to move the left margin up to the left edge of the paragraph. Then (assuming you have activated the wordwrap feature < F9 >) turn wordwrap on using the keyPad * command. Now use the Right <shft-alt> ◄ keystroke to move the right margin to the left. You will see the paragraph get thinner and thinner. Use the right <shft-alt> ► keystroke to make the paragraph fat again. 3.2 With wordwrap still on, put the cursor somewhere inside this paragraph. Note that left margin for this paragraph has not been moved to the left edge of the paragraph. Now use the Right <shft-alt> ◄ keystroke to move the right margin to the left. When the right margin bumps the text, everything gets out of whack. 3.4 Even worse, suppose the table below were part of a document you were working on, and by accident you put the cursor in the middle of it and used the right <shft-alt> ► keystroke to move the right margin to the right. Watch in horror as your table falls apart completely (do this now). ╔═══════════► Operation Table ◄════════════╗ ╟──────────────────────────────────────────╢ ║operation│ + - * / | & << >> ║ ║─────────┼────────────────────────────────║ ║literal │ 1 2 3 4 5 6 7 8 ║ ║int var │ 9 10 11 12 13 14 15 16 ║ ║function │ 17 18 19 20 21 22 23 24 ║ ╚══════════════════════════════════════════╝ 3.5 Best to turn wordwrap off now (Keypad *). It should be clear that care must be taken when going back and forth between wordwrap_on and wordwrap_off. If you do it right, it can be a powerful feature of the editor and can save you a lot of time. If you do it wrong, it can cause you real headaches. The main thing to remember is to be sure your margins are in the right place before you turn wordwrap on. ────────────────────────────────────────────────────────────────── 4.1 The Dmuse editor maintains three separate data buffers to aid in the storage and transfer of text within a window and between windows: the box buffer, the line buffer, and the stream buffer. The box buffer is operational only when wordwrap is off; the stream buffer is operational only when wordwrap is on. The line buffer is operational all of the time. 4.2 The stream buffer is so named because it stores text data as one long stream of text, as opposed to a set of lines (line buffer) or a rectangular area of the screen (box buffer). When data is put into the stream buffer, the line structure of that data is ignored; when data is retrieved from the stream buffer, the line structure (position of the line breaks) must be constructed from the current position of the margins. This can only be done when wordwrap is on; hence the stream buffer is only active when wordwrap is operating. 4.3 The mechanism for selecting data for insertion into the stream buffer is the stream highlight. The stream highlight works only when wordwrap is on. A stream highlight can be started by any of the four keystrokes: <shft> ◄, <shft> ▲, <shft> ►, or <shft> ▼. Once a stream highlight has been started, the starting position serves as one of the endpoints of the stream. The other endpoint is the current position of the selecting cursor. You can move the selecting cursor within margins using the same four commands: <shft> ◄ = move the selecting cursor to the left. <shft> ► = move the selecting cursor to the right. <shft> ▲ = up one line. <shft> ▼ = down one line. If the selecting cursor is outside the right margin, it behaves as if it were at the end of a line just inside the right margin. 4.4 Once a stream of text is highlighted, there are two things you can do. Delete will delete the highlighted stream. The text will be reformatted to fill in the space left by the deleted stream. <ctrl> Delete will delete the highlighted stream and place it in the stream buffer. The text will be reformatted to fill in the space left by the deleted stream. Note: in the current implementation of Dmuse, the stream buffer and the box buffer use the same storage space. This means that inserting material into the stream buffer will overwrite the box buffer, and vise versa. 4.5 To retrieve data from the stream buffer, you simply put the cursor where you would like the steam to appear and press <ctrl> Insert keystroke. The stream will be inserted into the text, irrrespective of whether insert mode is on or off. ────────────────────────────────────────────────────────────────── ┌─────────────────────────────┐ │ D I C T I O N A R I E S │ └─────────────────────────────┘ 1.1 Dictionaries have several potential uses. They can be designed as general, foreign language dictionaries; they can be designed as a combination of dictionary and commentary for specific works in a foreign language; and they can be used to include footnotes in a document. A major advantage in the way dictionaries are set up with Dmuse is that the user is able to construct his/her own dictionaries for whatever purposes he/she might have. Dmuse provides the access apparatus for such dictionaries. ▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂ 1.2 A dictionary consists of a set of records. Each record has two fields. The first field contains a key work; the second field contains text, which might contain a definition, a translation, or a pointer to another word in the dictionary. 1.3 Dictionaries are stored in files on the disk. In order for Dmuse to access a dictionary, the dictionary must be loaded into the Dmuse dictionary buffer. This is done by pressing F9 and choosing the "d" option. You will see that there are eight slots into which dictionaries can be loaded. For each of these, you use the plus (+) sign to cycle through the dictionaries that are available to load. At the moment, no dictionaries are included with Dmuse. 1.4 If a dictionary is loaded into one of the eight slots, you may access it through the commands: slot 1 KeyPad Enter slot 2 <shft> KeyPad Enter slot 3 <ctrl> KeyPad Enter slot 4 <ctrl-shft> KeyPad Enter slot 5 <alt> KeyPad Enter slot 6 <shft-alt> KeyPad Enter slot 7 <ctrl-alt> KeyPad Enter slot 8 <ctrl-shft-alt> KeyPad Enter What happens when you type one of these commands is that Dmuse takes the word that the cursor is currently under and searches for this word among the key words in the dictionary. If the word is found, a box opens at the bottom of the screen showing and word and the text (definition, etc.) associated with it. 1.5 Dictionaries can also be used to register footnotes for documents. There is a provision for the keyword field to contain a line number. When a line number is present, Dmuse will register a "find" only if the keyword occurs on the specified line number. For mechanism to work properly, you would need to load the proper dictionary for each document you were viewing. 1.6 The target application for the footnote feature described above is texts for people learning to read a foreign language. Suppose, for example, that you are learning latin and you want to read Caesar's Gallic Wars in the original text. The editor of that text might decide that you may need help with a certain gramatical construction. If each of the words in that construction are entered into the dictionary with the proper line number, then accessing the dictionary at one of these words would produce the translation of that particular passage. If the line number did not match, Dmuse would then look in the dictionary for a simple entry for the word in question. ────────────────────────────────────────────────────────────────── 2.1 At the moment, there are no dictionaries that work with Dmuse. This may change in time. However, it is possible for you to construct your own dictionaries. The first record in a dictionary is the title of the Dictionary. It is ignored by Dmuse. All subsequent records are dictionaary records. 2.2 A standard dictionary record has two parts: a key, and a definition. The key must start in column one and must be highlighted with the F6 function key (red). The key must be followed by one or more blanks (with no highlight). The definition field of the record may consist of either a regular (non-highlighted) definition, or a reference to another record in the dictionary. The reference is another key, and it must be highlighted with the F6 function key (red). 2.3 Since the maximum size of the dictionary box is 64 colunns, a definition may need to have some line breaks in it (if it is longer that 60 bytes). We use the "\" character as a line break. A definition may contain up to eight lines (seven line breaks). Example of a section of a dictionary: │ │angehen angehen = concern \ ging, gegangen │angehören angehören = belong to │Angehörig der Angehörig- = person belonging to │Angeklagt der Angeklagt- = defendant, accused │Angelegenheit die Angelegenheit, -en = affair │angenehm angenehm = pleasant, agreeable │ 2.4 It is also possible to have line specific entries in a dictionary. This allows one to enter commentary to a document or a specific translation of section of a foreign text. A line specific entry begins with the line number, followed immediately by the key. Both the line number and the key are highlight with the F6 function key (red). When looking up a key in a dictionary, Dmuse will first check the line specific entries and then the standard entries. For this reason, the line specific entries must be the first ones in the dictionary, and they must be in order of lines. Standard entries must be in alphabetical order. Example of line specific entries from a dictionary: │ │122lief lief nich mehr = wouldn't run any more │122nicht 122lief │122mehr 122lief │125den den Bären = Hotel Bären │125Bären 125den │ Notice that all three words in the phrase "lief nich mehr" are referenced in the dictionary (on line 122) but that definition does not need to be repeated three times. 122lief is used as a cross reference key to the single definition. 2.5 Dictionaries are time consuming to build, but a lot of fun to use. It is hoped that this application could open up new ways to teach foreign languages, and to enjoy reading works in their original text. ────────────────────────────────────────────────────────────────── ┌─────────────────────────────────┐ │ T H E I N I T F I L E │ └─────────────────────────────────┘ 1.1 When Dmuse starts up, it reads a file called INIT in the home directory for Dmuse (default = /usr/local/apps/disp). This file contains the values of several parameters that determine the configuration of Dmuse for your computer. You can load this file in a window and examine and/or change these parameters, if you wish. But you must use caution in doing this; otherwise the next time you try to run Dmuse, it might not work. 1.2 The INIT file consists of a minimum of 70 records. A sample INIT file is displayed below. Record # Contents ──────── ────────────────────────────────────────── 1 Starting Window Size: 100 x 43 (columns, rows) 2 Starting Window Position: 20, 20 (x, y) 3 Starting Window Font: 10 x 21 (width, height) 4 color[ 0] black 5 color[ 1] red 6 color[ 2] green 7 color[ 3] coral 8 color[ 4] steel blue 9 color[ 5] violet 10 color[ 6] dark turquoise 11 color[ 7] white 12 color[ 8] indian red 13 color[ 9] goldenrod 14 color[10] sienna 15 color[11] sea green 16 color[12] forest green 17 color[13] dark orchid 18 color[14] pink 19 color[15] blue 20 Window Title 0x60 --> Black on dark turquoise 21 Select Highlight 0x7f --> Blue on white 22 Select Highlight2 0x5f --> Blue on violet 23 Select Shadow 0x1f --> Blue on red 24 Dialog Border 0x10 --> Black on red 25 Box Highlight 0x10 --> Black on red 26 Window Text 0x07 --> White on black 27 Dialog Text 0x47 --> White on steel blue 28 Txt Highlight[ 1] 0x10 --> Black on red 29 Txt Highlight[ 2] 0x01 --> Red on black 30 Txt Highlight[ 3] 0x80 --> Black on indian red 31 Txt Highlight[ 4] 0xa7 --> White on sienna 32 Txt Highlight[ 5] 0x30 --> Black on coral 33 Txt Highlight[ 6] 0x90 --> Black on goldenrod 34 Txt Highlight[ 7] 0xe0 --> Black on pink 35 Txt Highlight[ 8] 0x02 --> Green on black 36 Txt Highlight[ 9] 0x20 --> Black on green 37 Txt Highlight[10] 0xc7 --> White on forest green 38 Txt Highlight[11] 0x50 --> Black on violet 39 Txt Highlight[12] 0x47 --> White on steel blue 40 Txt Highlight[13] 0x60 --> Black on dark turquoise 41 Txt Highlight[14] 0xd7 --> White on dark orchid 42 Txt Highlight[15] 0xb7 --> White on sea green 43 Graphics background: 7 White 44 Graphics bitplane 1: 0 Black 45 Graphics bitplane 2: 3 Coral 46 Graphics bitplane 3: 1 Red 47 Graphics bitplane 4: 15 Blue 48 MIDI Installed: N 49 Ibex Active: Y 50 Wordwrap Active: Y 51 Dictionary Active: N 52 Humdrum Support: 0 (values: 0=none, 1=standard, 2=extended) 53 Follow Links: N 54 ZPATH[0] (must be full path names) 55 DICTIONARIES[0] (file names only, maximum length = 12) 56 QPATH[0] (maximum path size = 99) 57 Maximum length of zbex temporary string = 10000000 58 Speed Parameter = 40000 59 Printer I default 60 type: LaserJet III | 61 port: 0 | 62 topline: 5 | 63 number of lines: 54 | 64 lines per inch: 6 | 65 left margin: 80 | 66 number of copies: 1 | 67 orientation: 0 | 68 font1: 12.0 Upright Medium Courier | 69 font2: 12.0 Upright Bold Courier | 70 font3: 12.0 Italic Medium Courier | 71 font4: >(10U>(s1p14.4v0S | 1.3 Record 1 gives the starting X Window size, expressed in columns and rows. 1.4 Record 2 gives the starting X Window position, expressed in <x,y> coordinates measured from the upper left corner of the Dislay. 1.5 Record 3 gives the starting Text font size, exressed in pixels. Allowed values at the moment include: 7 x 6, 8 x 12, 8 x 15, 9 x 18, 10 x 21, and 11 x 24. 1.6 Records 4 to 19 contain the 16 colors used by Dmuse. These color names are part of a standard UNIX list common to almost all UNIX and Linux workstations (PC's). You may make changes to this list, provided that you know the list of color names available on your computer. 1.7 Records 20 to 27 specify the color combinations for various parts of the Dmuse screen. For example, Window Text (here, White on black) is the main blackboard of Dmuse; Dialog Border (here, Black on red) are the colors of the Dmuse main border. The first number (0 to f) is the color of the background; the second number is the color of the foreground (text). 1.8 Records 28 to 42 contain the 15 possible color attributes for screen characters. Including the default "white on black," there are 16 combinations. 0. F5 4. <shft> F5 8. <ctrl> F5 12. <ctrl-shft> F5 1. F6 5. <shft> F6 9. <ctrl> F6 13. <ctrl-shft> F6 2. F7 6. <shft> F7 10. <ctrl> F7 14. <ctrl-shft> F7 3. F8 7. <shft> F8 11. <ctrl> F8 15. <ctrl-shft> F8 F5 is for ordinary text (no highlight), and this attribute is already given in record 26. The attributes for the remaining conbinations are given in the order shown. For example, the colored boxes above were made by pressing <F6>. In the case of the example INIT file, the attribute for <F6> (Txt Highlight[1]) is hexidecimal 10; i.e., black text on a red background. If these are the colors you see, then your INIT file has the same value for Txt Highlight[1]. 1.9 Records 43 to 47 specify the colors (0 = black, 1 = dark blue, etc.) used for the graphic bitplanes. Dmuse can address four bitplane colors on top of a background (how this is done is explained in the section on Zbex). 1.10 Record 48 indicates whether or not a MIDI interface is installed on the system, and if so, the name of the port, the type of device, and the path location of the midi_in program. 1.11 Records 49 to 51 contain the flags that tell whether Zbex, Wordwrap, and the Dictionaries are active or not. 1.12 Record 52 indicates the level of Humdrum support. Humdrum is a set of Linux type commands (bash shell), which are useful in analyzing files containing musical data in various formats. 1.13 Record 53 specifies whether directory listings should follow links to actual files/directories, or merely list links as links. 1.14 Record 54 gives the number of sub-directories in which the Ibex compiler will look for programs. In our example, the number is zero; but normally it will be one or greater. The actual full path directories follow in succeeding records. The path names start in column 3 (with the / character). Normally when you run an Ibex program, you must first type "zz" to call the compiler or "ww" to call the interpreter. However, if you have an Ibex program that you expect to use many times (a utility program, for example) you may want to put the program in a designated sub-directory, where Ibex will know to look. Then all you need to do to run the program is to type the name of the program (actually the name of the file containing the program). For more information on this feature, see the section on Ibex in this document. 1.15 Record 55 specifies the dictionaries which should be loaded at startup time, and also any other dictionaries that Dmuse should know about. All dictionary files specified in this record must exist in the sub-directory DICT of your Dmuse home directory. For more information on this subject see the section on dictionaries in this document. 1.16 Record 56 specifies the number virtual disk assignments. In this example, the number is zero, but normally it might be as large as 20. Shown below is a more realistic case: QPATH[19] (maximum path size = 99) A:/ --> /mnt/floppy C:/ --> / D:/ --> /usr/local/apps/arc E:/ --> /home/wbh F:/ --> /usr/local/apps/musedata/baroque (bach, handel, corelli, telemann) G:/ --> /usr/local/apps/musedata/classical (haydn, mozart, beethoven) H:/ --> /usr/local/apps/musedata/romantic (schubert, brahms) I:/ --> /usr/local/apps/musedata/other (everything else) J:/ --> /usr/local/apps/xdata/part1 K:/ --> /usr/local/apps/xdata/part2 L:/ --> /usr/local/apps/xdata/part3 M:/ --> /usr/local/apps/release N:/ --> /usr/local/apps/musprint O:/ --> /usr/local/apps/temp P:/ --> /usr/local/apps/research Q:/ --> /usr/local/apps/editions R:/ --> /usr/local/apps/disp S:/ --> /mnt/zip T:/ --> /mnt/cdrom Column 3 contains a "disk" letter. These could be capital and in alphabetical order. Columns 4 - 11 read ":/ --> " Columns 12 -- contain the path name to be assigned to the specified disk letter. Comments may follow after one or more blanks. Dmuse maintains a current working directory for each of these virtual disks. This makes it easy to jump between various diverse branches of the file system tree. one time on your system. Dmuse needs to know this number in order to keep Ibex programs from trying to open too many files. 1.17 Record 57 specifies a maximum length for storing temporary strings in Ibex. See the section on Ibex in this document. 1.18 Record 58 contains the cursor speed parameter (not used in Linux) 1.19 Records 59 to 71 provide default parameters for a printer, if one is connected to the system. The parameters are for use by the "print screen" command. At the moment, only LaserJet compatable printers are supported in this capacity. For a description of these parameters, see the section on the print-screen command in the Using the screen editor help topic or the section on printer parameters in the Editor quick reference help topic. ────────────────────────────────────────────────────────────────── ┌─────────────┐ │ Z B E X │ └─────────────┘ 1.1 Each of the 30 Dmuse windows can be made to behave like a computer terminal. The command to make this happen is <shft> KeyPad *. This command actually toggles the connect mode flag; i.e., press it again and the window stops acting like a terminal. 1.2 The main thing that is different with connect mode on is the action of the Enter key. With connect mode on, the Enter key will send the contents of the line the cursor is on to the computer as an input. How the computer responds to that input depends on the current state of the window. When the window is waiting for you to give it the name of a program to run, it will present the prompt, "Ready for program." If you simply press the Enter key, that is, enter a blank line, the window will respond with the lines: │Not found │Ready for program │╱ This happens because Dmuse does not recognize a blank line as the name of a program. If you enter a line that Dmuse does recognize as a program, it will tranfer control to that program; i.e., that program will begin to run. 1.3 There are several programs that Dmuse will recognize, but we should start with the main one, namely, the Zbex compiler and interpreter. This program will compile an Zbex program that you specify and then run that program. The Zbex compiler/interpreter is called by entering "zz". If you type "zz <Enter>", you will see the following lines appear in your window. │zz │Current Library is <current directory> │Program file? │╱ 1.4 The prompt for a program file is the way the Zbex compiler asks you for the Zbex program you want to run. You have three options: (1) you may enter the name of a file, or (2) you may enter a line with the form "*<window number>", e.g., *6, or (3) you may enter a blank line. If you enter the name of a file, the Zbex compiler assumes that the file contains an Zbex program, and it will attempt to compile that program into an executable module that can be handed off to the interpreter. If you enter "*<window number>", the Zbex compiler assumes that the specified window contains the Zbex program you want to run. If you enter a blank line, the Zbex compiler expects you to enter the program, line by line in the current window. 1.5 Let's follow the third option for a moment. Suppose you enter a blank line at the "Program file?" prompt. Then you will see the following lines in your window: │zz │Current Library is <current directory> │Program file? │ (blank line entered here) │Input program from terminal. │╱ 1.6 You are now expected to enter a program. Let's suppose you enter the following two-line program: putc Hello World run You should then see the following lines in your window: │zz │Current Library is <current directory> │Program file? │ │Input program from terminal. │putc Hello World │run │** S=2, P=8, L=231, M=410 ** │Hello World │Ready for program │╱ The line with the numbers tells you that the program successfully compiled; the next line "Hello World" is the output from the program; and the "Ready for program" prompt tells you that the window is again waiting for you to give it the name of a program to run. ────────────────────────────────────────────────────────────────── ┌────────────────────────────────────────────────┐ │ 2. Zbex programs in files and in other windows │ └────────────────────────────────────────────────┘ 2.1 The first option (Zbex program in a file) and the second option (Zbex program in another window) work in essentially the same manner as the third option described above (Zbex program entered in the current window). 2.2 Let's suppose that you create a file called "TEST.Z" in the current directory, and that this file holds the following two records: putc Hello World run Then if you type "zz <Enter>", and enter "TEST.Z" at the "Program file?" prompt, you will see the following lines in your window: │zz │Current Library is <current directory> │Program file? │TEST.Z │** S=2, P=8, L=231, M=410 ** │Hello World │Ready for program │╱ 2.3 As before, the line with the numbers tells you that the program successfully compiled; the next line "Hello World" is the output from the program; and the "Ready for program" prompt tells you that the window is again waiting for you to give it the name of a programto run. 2.4 Suppose window 4 contains the following two line: putc Hello World run You can run this program from your current window by typing "*4" at the "Program file?" prompt. You will then see the following lines in the window: │zz │Current Library is <current directory> │Program file? │*4 │** S=2, P=8, L=231, M=410 ** │Hello World │Ready for program │╱ 2.5 This concludes the documentation on how to compile and run an Zbex program. What follows now is a description of the Zbex programming language. ────────────────────────────────────────────────────────────────── ┌─────────────────────────┐ │ 3. Properties of Zbex │ └─────────────────────────┘ 3.1 The Zbex programming language is very similar to the Pascal programming language. If you have programmed in Pascal, you will find learning Zbex quite easy. There are two cautions you need to keep in mind. (1) While Zbex statements on the surface look the same as Pascal statements, the implimentation of the statements (that is, what the program actually does) may be slightly different in some cases. (2) Zbex is not as versatile as Pascal. There are many things you can do with Pascal that you cannot do with Zbex. Zbex, for example, does not allow you to use pointers. You cannot compile and link separate modules with Zbex. This means that you cannot build programs from separate pieces. 3.2 If Zbex is essentially a scaled down version of Pascal, what are its advantages? Why not just program in Pascal? The answer to this question has several parts. (1) Zbex compiles into an intermediate language, which is executed by an interpreter. The advantage is that your Zbex program never really controls the computer; the interpreter controls the computer, with your Zbex program telling the interpreter what to do. If your Zbex program asks the interpreter to do something that is illegal, that is, something that would crash the system, the interpreter won't do it; instead, the interpreter will flag the offending line of code in your Zbex program, giving you information about what was wrong, and then halt the execution of your program. The remaining windows of Dmuse will be unaffected by this. (2) All Zbex programs require the same interpreter. The interpreter has been set up to handle more than one Zbex program at the same time. It does this by switching between programs after executing a specified number of instructions. Zbex programs, once started, will continue to run in the background when you change to another window. You can use the Dmuse editor in one window, while several Zbex programs are running in other windows. (3) Zbex was designed primarily as a language to manipulate string variables. In Zbex, string variables actually have two components: (1) the current run-time length of the string, and (2) the actual characters in the string. A string variable may be declared to have a maximum length of 100 characters, but the actual length of the string during the running of the program may vary from zero to 100 characters. (4) Zbex not being a standard language has allowed us to add extra features, without running the risk of confusing people who are use to a standard language. Features added to Zbex include, (1) specialized functions to perform common tasks more quickly, (2) specialized variable types such as random access tables, (3) specialize instructions for applications in music. Included in this group are commands to display musical notation in graphics mode and commands to control a (Roland) MPU 401 type midi card. (5) The real purpose of Zbex is to allow you to write "throw away" programs quickly and easily. It has been estimated that 80 to 90 percent of the programs written in course of doing research on the computer are used once and then thrown away. In this type of situation, it is important to have (1) good diagnostics at compile time, (2) good run-time debugging tools, (3) quick turnaround between successive versions of a program, and (4) a safe execution enviroment that will not trash the system. Zbex has all of these features. 3.3 In spite of Zbex's real purpose being the ability to write "throw away" programs, I have found Zbex to be an excellent language for writing software for complicated tasks such as music printing. It is hard enough to get such programs to run properly, without having to worry about whether I have overwritten some critical section of computer memory in the process. Zbex allows me to check out the logic of a program in a friendly environment, and later to convert that logic to a less forgiving language such as C. This two-step process can save a lot of time in the long run. ────────────────────────────────────────────────────────────────── ┌──────────────────────────┐ │ 4. Zbex utility programs │ └──────────────────────────┘ 4.1 Dmuse comes with six Zbex utilities program. These programs are in the library zprogs of the Dmuse home directory. Programs in this library can be run simply by typing their name (without the .z extension) and Enter. You can add your own Zbex programs to this library and they, too, can be run simply by typing the name of the program. Like the programs already in the library, they must have a .z extension. 4.2 Six Zbex Utility Programs ──────────────────────────────────────────────────────────── dcom This program will compare the contents of two libraries and report on all differences. The contents of all sub-libraries will be examined. diff This program will compare two files and report on all differences. The program works only when the two files are nearly the same. fcompare This is a program to compare all files in two libraries with a specified extension. The files will be printed out with the labels BAD or OK, depending on whether they are identical or not. The files in library 1 will serve as the model list. lost This is a program to find a specified string in a set of path names. It is called lost, because it is useful in helping you to find a file whose location (and possibly whose complete name) you have forgotten. qed This is a line editor that allows you to edit very large flat ASCII files that won't fit in a Dmuse window. The maximum number of records that will fit in a Dmuse window is 98000. search This program will search for a specified string in all files in the current library having a particular extension. This program is useful in searching a set of .c files for all examples of the use of a particular variable. 4.3 The next several chapters describe the details of the of the Zbex programming language. After you have studied this documentation and have tried writing a few programs, you might want to return to this section and examine the six programs above to see how they use various features of the Zbex language. ────────────────────────────────────────────────────────────────── ┌───────────────────────────┐ │ 5. Declaration statements │ └───────────────────────────┘ 5.1 The Zbex language recognizes four data types: strings, bit-strings, integer numbers, and real numbers. Each of these data types can have two forms: literal (fixed for the life of the program) and variable. Literal data types need no introduction; the compiler accepts them wherever they are found. Variable data types (called variables) must be declared (identified to the compiler) before they can appear in a program. Strings: A string is a sequence of bytes of specified length. The length is a integral part of the data type. Literal strings are enclosed in double quotes. Examples: "TEXT.Z", "Now is the time.", "123435.6789" Note that the contents of a string is case sensitive. Variable strings are declared using the str statement, following by a list of variables (separated by commas). Each variable name must be followed immediately by a dot and a number. The number tells the compiler how many bytes of compute memory to allocate for the string variable. Example: str temp.100, file.100, big.10000000 This statement identifies three string variables to the compiler: (1) temp, with maximum length of 100 bytes, (2) file, with maximum length of 100 bytes, and (3) big, with maximum length of ten million bytes. At the onset of a program, all string variables are assigned a run-time length of zero. We call this the null string. When a string takes on a value other than the null string, this value has two components, a length, and a sequence of bytes. Example: temp = "Now is the time " In this statement, the string variable, temp, is assigned a length of 17 and the byte seqence Now is the time . Bit strings: A bit string is a sequence of bits of specified length. The length is a integral part of the data type. Literal strings are represented as a stream of zeros and ones enclosed in double quotes. Examples: "010011", "0000000101010101", "00000000000" Variable bit strings are declared using the bstr statement, following by a list of variables (separated by commas). Each variable name must be followed immediately by a dot and a number. The number tells the compiler how many bits of compute memory to allocate for the bit string variable. Example: bstr test1.100, test2.100, bigbit.5000000 This statement identifies three bit string variables. (1) test1, with maximum length of 100 bits, (2) test2, with maximum length of 100 bits, and (3) bigbit, with maximum length of five million bits. At the onset of a program, all bit string variables are assigned a run-time length of zero. We call this the null bit string. When a bit string takes on a value other than the null bit string, this value has two components, a length, and a sequence of bits. Example: test1 = "10101010101" In this statement, the bit string variable, test1, is assigned a length of 11 and the bit seqence 10101010101. Integer numbers: An integer number can have any value from -2,147,483,648 to +2,147,483,647. Literal integers can be represented either in decimal format or in hexidecimal format. Examples: 0 10 010 +20 -50 0xff 0x00ff 0x7fffffff The second and third examples are equivalent. The plus sign in the fourth example is optional. The minus sign in the fifth example is not optional, and it must come immediately before the number. The sixth and seventh examples are equivalent and have the decimal value of 255. The eighth example is the largest positive value that can be taken by an integer number. Variable integers are declared using the int statement, following by a list of variables (separated by commas). Example: int a,b,d, d,e,f This statement identifies six integer variables to the compiler: a, b, c, d, e and f. At the onset of a program, all integer variables are assigned a run-time value of zero. Real numbers: A real number can have up to sixteen significant digits and take on absolute (plus or minus) values from 1.0 * 10(exponent -309) to 9.9 * 10 (exponent 308) as well as the value 0. Literal real numbers be represented either in fixed point format or floating point format. Examples: 0.0 1.0 1.000 +2.34e+20 -3.54e-30 The second and third examples are equivalent. The plus signs in the fourth example are optional. The minus signs in the fifth example is not optional, and they must come immediately before the numbers thay modify. Variable real numbers are declared using the real statement, following by a list of variables (separated by commas). Example: real x,y,z This statement identifies three real variables to the compiler: x, y and z. At the onset of a program, all real variables are assigned a run-time value of zero. ────────────────────────────────────────────────────────────────── ┌──────────────────────────┐ │ 6. Assignment statements │ └──────────────────────────┘ 6.1 The most common way to set the value of a variable is with an assignment statement. Assignment statements take the form <variable> = <expression>. The two sides of an assignment statement must have data types of the same type. The expression on the right may have one or more elements. For strings and bit strings, elements in an expression are combined using the concatenate operator (//). For integers and real numbers, elements are combined using one or more of the arithmetic operators: integer and real integer only ──────────────────── ──────────────────── + = add & = and - = substract | = or * = multiply >> = shift right / = divide << = shift left In the absense of parentheses, arithmetic operators are processed in a left to right fashion, i.e., not according to the rules of algebra. An element of an expression may be a literal, a variable or a function. Functions will be covered later. Examples: Strings: str temp.80,name.80 name = "<your name>" temp = "Good afternoon, " // name // ", I am pleased " temp = temp // "to meet you." In this case, the string temp has the value Good afternoon, <your name>, I am pleased to meet you. Note that the concatenate operator (//) must have a blank on either side of it. Bit strings: bstr test1.80, test2.80 test1 = "1010101" test2 = test1 // "000" // test1 In this case, the bit string test2 has the value 10101010001010101 Intergers: int a,b,c a = 10 b = 20 c = a >> 2 * b + 4 In this case, the value of c is 10 shifted right 2 = 2, times 20 = 40 plus 4 = 44 Real numbers: real x,y,z x = 10.0 y = 5.0 z = x - 3.0 / y * 3.0 In this case, the value of z is 10.0 minus 3.0 = 7.0, divided by 5.0 = 1.40, time 3.0 = 4.2 For interger and real number operations, you can use parentheses to control the order in which operations are performed. For all assignment statements the right hand side is evaluated before the left hand side is changed. Examples: 1. bstr test1.80 test1 = "1010101" test1 = test1 // "000" // test1 In this case, the bit string test1 has the value 10101010001010101 2. int a a = 10 a = a + 1 In this case, the value of a is 11 ────────────────────────────────────────────────────────────────── ┌────────────────────────────────┐ │ 7. The run and stop statements │ └────────────────────────────────┘ 7.1 The last statement in an Zbex program must be the run statement. The program will compile but will not run without this statement. Any code or text below (after) the run statement in a file or in a window will be ignored. 7.2 You can cause your program to terminate at any location with the stop statement. The compiler automatically compiles a stop statement before the run statement and before any procedure statement. This prevents program control from entering a procedure by accident. ────────────────────────────────────────────────────────────────── ┌─────────────────────┐ │ 8. Input and output │ └─────────────────────┘ 8.1 After mastering the declaration statements and assignement statements, the most important Zbex instructions to learn are the input and output instructions. getc = get data from the next line entered from the screen. getc may stand alone as an instruction, or it may take variable arguments. If it stands alone, the program will stop running until the Enter key is pressed. If it has arguments (variables), Zbex will attempt to assign values to the variables using the contents of the next entered line. Examples: int a,b str temp.10 real x getc a x b (entered line = "10 2.0 15") In this case, a will be assigned the value 10, x the value 2.0, and b the value 15 getc temp a b (entered line = "1 2 3 4 5 6 7 8 9") In this case, temp will be given the value 1 2 3 4 5 (i.e., the first 10 bytes of the line, a will be assigned the value 6, and b the value 7. You may reset the point at which Zbex processes a line by using the .t<number> format command. For example, .t1 will reset the process pointer to column one of the input line. Example: getc temp .t1 a b (entered line = "1 2 3 4 5 6 7 8 9") In this case, temp will be given the value 1 2 3 4 5 (i.e., the first 10 bytes of the line, a will be assigned the value 1, and b the value 2. If you respond to a getc by entering two exclaimation points (!!) at the begining of a line, your Zbex program will terminate. This is an important fact to remember, since there are only two ways to stop an Zbex program once it has been started (the other is with the <ctrl> Break key). putc = put data to screen at the point where the cursor is. putc may stand alone as an instruction, or it contain an output string. If the output string has variable arguments, they must be preceded immediately the ~ character and followed immediately by a blank. If the output string ends with three dots (...), no carriage return is given (the cursor stays on the same line); otherwise the cursor is moved to the next line after writing the output string to the screen. Examples: int a,b str temp.10 real x a = 10 temp = "Hello" putc temp (writes temp to the screen) putc ~temp (writes Hello to the screen) putc a (writes a to the screen) putc ~a (writes 10 to the screen) putc Hello ... putc there. (writes Hello there to the screen) There are several format commands that can be used to control how things are written to the screen. All format commands start with a dot (.) and end with a blank. Format commands may be combined. Examples: .t20 = move cursor to column 20 .t(a) = move cursor to column <a> where a is declared as an integer variable and has some value. .w6 = right justify integer and real variables in a space with 6 columns .x = display integers in hexadecimal format .e4 = display real numbers in floating point notation with 4 digits to the right of the decimal point .f2 = display real numbers in fixed point notation with 2 digits to the right of the decimal point .c1 = precede integers with a dollar sign ($). .c(a) = precede integers with a <depends on a> sign. .d2 = display integers as if they had two digits to the right of a decimal point a = 120 putc .c1d2 ~a (line out = $1.20) b = 4 putc .c(b)d2 ~a (line out = DM1.20) There is also an easy way to put out any byte value from 0 to 255 using the .b command. This command follows the same rules as the format commands; i.e., it starts with .b, followed by an literal integer or an integer variable in parentheses, followed by a blank. Example: putc .b27 A... This statement puts out the two byte sequence <esc>A, which happens to be the command for moving the cursor up one line (for a list of all escape sequences, see the section on controlling the (text) display with putc). putp = put data to the printer port LPT1. putp works the same way as putc, except that the output is directed to the printer port LPT1 (to your printer, if it is on). 8.2 At this point, you should try experimenting with writing Zbex programs. You know how to enter numbers and strings into the computer, how to combine them, and how to print them out on the screen. The last instruction in an Zbex program must be "run" See example in paragraph 1.6. 8.3 When you have mastered the getc and putc commands, it is time to learn how to get records from a file and how to put records into a file. To operate on a file, you must first open it. A file can be opened for seven different types of operations: 1 = open file for use by getf (get ASCII records sequentually from the file), 2 = open file for use by putf (put ASCII records sequentually to a file. Write over any previous contents), 3 = open file for use by putf (append ASCII records onto an existing file; no contents are over written), 4 = open file for random read only (for use with files with read permission but no write permission), 5 = open file for use by random read and write (blocks of arbitrary size), 6 = open file for use by write (create new file), 7 = open file for use by write (append blocks of arbitrary size). 8.4 When you open a file, you must give it a tag. The tag is a number from 1 to 9. You may have up to nine files opened at one time. Any operation (getf, putf, read, write, or close) on an opened file must refer to that file by its tag. The open instruction must supply three pieces of information: (1) the tag for the file you are opening (integer from 1 to 9), (2) the type of operation you want to perform on the file (integer 1,2,3,4,5,6 or 7), (3) the name of the file you want to open. Examples: open [1,2] "outfile" This will open a file in the current directory. The name will be outfile. The file will be given the tag of 1. The file may exist already, or it may be new. The file is opened for new output, specifically putting records sequentially to the file. open [6,4] "bigfile" This will open a file called "bigfile" in the current directory. The file must already exist and must have read permission. The file will be given the tag of 6. The file is opened for reading ONLY at random (arbitrary) points in the file. The size of the blocks read is determined at run time. open [3,5] "bigfile" This will open a file called "bigfile" in the current directory. The file must already exist and must have BOTH read and write permissions. The file will be given the tag of 3. The file is opened for reading and/or writing at random (arbitrary) points in the file. The size of the blocks read or written is determined at run time. str filename.80 filename = "infile" open [5,1] filename This will open a file called "infile" in the current directory. The file must already exist. The file will be given the tag of 5. The file is opened for getting ASCII records sequentially from the top of the file. It sometimes happens that the open command fails. This can occur for a variety of reasons. The file might not exist (for reading); permission might be denied (for reading and writing); permission to create a new file (for writing) might be denied; etc. When this happens, the default behavior is for Zbex to prompt the user for a new (different) file name. This will suspend the operation program until a new name is supplied. Sometimes, however, the user does not welcome this interruption; if the file or directory can't be opened, the user wants the program to ignor the open request and move on. This will happen if bit 12 of the zoperation flag is set = 1. (see Section 18.3 on how to do this using the setflag instruction). In this case, how does the user's program know that open has failed, and why? Answer, Zbex sets the special variable err to one of the following values: 0 = open was successful 1 = cannot expand to full file name 2 = file already open somewhere else 3 = cannot open the file at all 4 = not a file or a directory 5 = unable to read directory 6 = read access on file is denied 7 = cannot open file for writing 3 = cannot open the file at all 8 = writing allowed only on regular files 9 = read/write access on file is denied 10 = write access on file is denied 11 = cannot create new file Program control moves on to the next Zbex instruction, which almost certainly would be to test the value of err. (See Section 17.3 for more on the err special variable) 8.5 When you are finished with a file, you should close it. Closing a file frees up the tag for further use. Example: close [5] This will close the file whose tag is 5. 8.6 The instructions for getting and putting ASCII records sequentially to a file work the same way as those for getting and putting lines to the screen. The only extra item of information required is the file tag. Examples: getf = get data from the next record in a file. getf may stand alone as an instruction, or it may take variable arguments. If it stands alone, the program will get the next record in a file, but the contents of the record will be ignored. If getf has arguments (variables), Zbex will attempt to assign values to the variables using the contents of the record. Examples: int a,b str temp.10 real x open [5,1] "<name>" getf [5] a x b (if next record = "10 2.0 15") a will be assigned the value 10, x the value 2.0, and b the value 15 getf [5] temp (if next record = "ABCDEFGHIJKLM") temp will be given the value ABCDEFGHIJ, (i.e., the first 10 bytes of the record. The rest of the record will be ignored. getf retrieves records sequentially from a file. But what happens when there are no more records to retrieve? In Zbex, trying to retrieve beyond the last record will cause control to jump to the label eofx, where "x" is the tag (digit from 1 to 9) given to the file when it was opened. If the label is not present in the program, the program will terminate when it comes to the end of the file. In the example above, [5] was the tag assigned to the file when it was opened, and, after getting the last record, control will jump to eof5: if it exists. putf = assemble the data in the output string into a record and append it to the output file referenced by the tag. putf may stand alone as an instruction, or it contain an output string. If the output string has variable arguments, they must be preceded immediately the ~ character and followed immediately by a blank. If the output string ends with three dots (...), the record is assembled but not put to the file (yet). The data from the next putf statement is append to this record. Examples: int a,b str temp.10 real x a = 10 temp = "Hello" putf [1] temp (adds a record containing the string "temp" to file [1]) putf [1] ~temp (adds a record containing the string "Hello" to file [1]) putf [1] a (adds a record containing the string "a" to file [1]) putf [1] ~a (adds a record containing the string "10" to file [1]) putf [1] Hello ... putf [1] there. (adds a record containing the string "Hello there." to file [1]) The format commands described for putc also work for putf. 8.7 At this point, you should experiment with opening files for putting and getting records. Try getting records first. One possible program you might try is this. str file.80, rec.120 putc File name? getc file open [1,1] file getf [1] rec putc ~rec close [1] run Can you guess what this program does? 8.8 The read and write commands allow you to create and access files which are not organized as a sequence of ASCII records. We call these files binary files, because there is no limitation placed on value of the bytes they contain. (ASCII bytes normally have values between 32 and 255). read = read a block of data from a file into a string The read command requires three pieces of information: (1) the file tag number, (2) the point in the file where you want to start reading, and (3) the string where you want the read data to be placed. The read starting point (2) may be specified explicitly or may be implied. This gives rise to two formats, (a) and (b), for the read instruction: (a) read [<num>] string <num> is file tag number, start is implied (b) read [<num>,<start>] string <start> is starting point Note: <start> = 1 indicates start reading at the first byte The number of bytes read is determined by the run-time length of the destination string. If the run-time length is zero, no bytes are read. Each file that is open for reading has a special variable called the read pointer. When the file is first opened, Zbex automatically sets this (read pointer) variable to 1. (i.e., pointing at the first byte in the file). If a read instruction is encountered in the (b) format above, the read pointer is set to the value of <start> before the read takes place. And after any read takes place, the read pointer is set to the byte (in the file) following the last byte read. Thus it is possible to used both formats of the read instruction for a file open for reading. In practice, however, format (a) is best suited for sequential reading applications and format (b) is best suited for random reading. Can you guess what the following program would do? str file.80, rec.120 putc File name? getc file open [1,5] file rec = pad(10) read [1,1] rec putc ~rec close [1] run Answer: It will open and try to read the first 10 bytes of the file you specify. The result is sent to the screen as an ASCII string. write = write a block of data to a file. If a file is open for both read and write (type 5 access), the write instruction requires three pieces of information: (1) the file tag number, (2) the point in the file where you want to start writing (1 = first byte), and (3) the string containing the data you want to write. If a file is open for write only (type 6 or type 7 access), then the write instruction requires only two pieces of information: (1) the file tag number, and (2) the string containing the data you want to write. In this case, the data will be appended to whatever has already been written to the file. Example: str file.80, rec.120 putc File name? getc file open [1,6] file rec = "This is a data string. " write [1] rec putc ~rec close [1] run This program tries to open the file you specify for writing. If the file does not already exist, it is created; if it does exist, it is over-written. If successful, the file will consist of 26 bytes: the 24 byte string above, and the line termination sequence CR/LF (ascii-10, ascii-11) ────────────────────────────────────────────────────────────────── ┌─────────────┐ │ 9. Comments │ └─────────────┘ 9.1 You can include comments in your Zbex code. In Zbex, each statement occupies exactly one line. The compiler scans the line from left to right. If it encounters the character pattern /*, it treats everything that follows as a comment. If a line has a star (*) in column one, or if column one is highlighted with a color, the entire line is treated as a comment. The latter feature makes it easy to comment out (change code lines to comment lines) whole sections of code. You simply use the box highlight to highlight column one of all the lines you wish to comment out and then press F6 to turn that column red. ────────────────────────────────────────────────────────────────── ┌────────────────┐ │ 10. Subscripts │ └────────────────┘ 10.1 Subscripts occur in two contexts: partial strings and array type variables. 10.2 All four data types (strings, bit strings, integers, and real numbers) can occur as array type variables. An array may have from one to eight dimensions. The size of each dimension must be stated in the declaration statement. Examples: str temp.80(4) One dimension, size = 4. Here we have four string variables, temp(1), temp(2), temp(3) and temp(4), each with maximum length of 80. bstr bits.32(2,3) Two dimensions, size1 = 2, size2 = 3. Here we have six (2 x 3) bit string variables, bits(1,1), bits(1,2), bits(1,3), bits(2,1), bits(2,2) and bits(2,3), each with maximum length of 32 bits. int t(2,3,2) Three dimensions, size1 = 2, size2 = 3, size3 = 2. Here we have twelve (2 x 3 x 2) integer variables. real x(100) One dimension, size = 100. Here we have one hundred real number variables. 10.3 When referring to an element of an array variable within an Zbex program, the numbers inside the parentheses (called subscripts) must be integer types, either literals, variables, functions, or some combination of the these (integer expression). Zbex does not allow you to refer to an entire array; neither does it allow you to refer to a single row or a single column in an array. Examples: int s(4,6), t(4,6), u(4) s = t (not allowed) s(*,*) = 0 (not allowed) t(*,3) = u(*) (not allowed) In Zbex, you must refer separately to each element in an array. To set all elements of s in the above example to 0 you would write: loop for i = 1 to 4 loop for j = 1 to 6 s(i,j) = 0 repeat repeat 10.4 Subscripts can also be used to denote sub-strings within a larger string. In this case, the subscript(s) appear within curly brackets {}. Examples: str temp.80 temp = "For the last time, Mr. Smee, take the princess home!" temp{4,11} Start at position 4 and include 11 characters. " the last t" temp{4..11} Include all characters from positions 4 to 11. " the las" temp{11} character at position 11 "s" temp{11..} All characters from position 11 to the end "st time, Mr. Smee, take the princess home!" 10.5 When string subscripts occur in a variable on the left side of an assignment statement, they reference the part of the string that is to be replaced by the right hand side. Example: str temp.80 temp = "For the last time, take the princess home!" temp{18} = ", Mr. Smee," This statement will replace the comma at position 18 of temp with the string ", Mr. Smee,". The result will be: "For the last time, Mr. Smee, take the princess home!" ────────────────────────────────────────────────────────────────── ┌─────────────────────┐ │ 11. Program control │ └─────────────────────┘ 11.1 An essential feature of any computer language is the ability to branch based on some condition. Zbex provides two mechanisms for branching: loops and the if statement. 11.2 There are three types of loop statements: (1) the simple loop, (2) loop while a relation is true, and (3) loop with a counter. Every loop statement must be balanced with a repeat statement. All lines of code between a loop statement and its associated repeat statement are considered to be inside the loop by the compiler. You may not use a goto statement to jump into the interior of a loop from anywhere outside the loop. Examples: int i,j loop This is a simple loop with no exit i = i + 1 condition. The way it is written, repeat program control will never leave the loop. We call this an infinite loop. loop for i = 1 to 10 This is a loop with a counter. It putc ~i will put the numbers 1 to 10 on the repeat screen. j = 5 This example illustrates an important loop for i = 1 to j point about Zbex. When a counting j = j - 1 loop statement is encountered at run repeat time, the interpreter evaluates the limits and fixes them. Nothing inside the loop can change the limits. In this example, the loop will execute 5 times, even though the value of j is subsequently changed inside the loop. loop for i = 1 to 9 step 3 The output from the code inside putc ~i the loop will be the numbers repeat 1, 4, and 7. The last putc putc ~i statement will put out the number 10. The repeat statement causes the counter (i) to be incremented and then tested. If the test fails,control passes to the statement beyond the repeat. i = 0 loop while i < 5 This loop will execute five times. i = i + 1 Upon exit, the value of i will be 5 repeat i = 0 j = 10 loop for i = 1 to j The output from this code will be i = i + 1 the numbers 2, 4 and 6. Note that putc ~i the value of the counter can be repeat while i < 5 changed inside the loop. Also note that the exit condition in this case is provided by the repeat statement, not the loop statement. Had j been set to 4 instead of 10, the output would have been the numbers 2 and 4, and the exit condition would have come from the loop statement. 11.3 The if statement in Zbex is part of an if-else-end grouping. The else is optional. Zbex uses the if statement to test a simple condition. If the condition is true, control passes to next statement in line. If the condition is false and there is an else statement in the group, control passes to the statement following the else statement; otherwise control passes to the statement following the associated end statement. Examples: int i loop for i = 1 to 5 The output from this code will if i > 3 be the numbers 0, 0, 0, 4 and 5. putc ~i else putc 0 end repeat loop for i = 1 to 5 The output from this code if (i > 2 and i < 5) or i = 1 will be the numbers 1, 3 putc ~i and 4. Observe the use of end and and or in this example. repeat These are called boolean operators and can occur only in if and while statements. Also observe the use of parentheses to establish the order of the boolean operations. 11.4 The normal way to transfer program control to a different part of a program is with the goto statement. The goto statement takes a label as an argument. You may not use a goto statement to jump into a loop or to jump into an if-else-end group. You may use goto to jump around inside a loop or to jump out of a loop (as long as you are not jumping into another loop). Examples: int i i = 1 This code does precisely the same thing AA: as the loop code below. Notice that if i <= 5 the label AA must begin in column one putc ~i and must be followed by a colon (:). i = i + 1 goto AA end loop for i = 1 to 5 putc ~i repeat 11.5 Zbex does not have case statements (these are a feature of both the Pascel and the C programming languages). But Zbex does provide a means for jumping to different points in a program depending on the value of an integer. To do this, we have implemented a variable type called a label. All label variables must be declared using the label declaration statement. All labels must be one dimensional arrays. To illustrate how label variables work, we show two pieces of code, each doing the same thing. int i int i loop for i = 1 to 5 label A(5) if i = 1 loop for i = 1 to 5 putc Eleanor goto A(i) else A(1): putc Eleanor if i = 2 goto B putc Walter A(2): putc Walter else goto B if i = 3 A(3): putc Jim putc Jim goto B else A(4): putc Bill if i = 4 goto B putc Bill A(5): putc Mary else goto B putc Mary B: repeat end end end end repeat The second piece of code is not only more compact, it is much faster because the test which determines the name to put out for each value of i happens only once, whereas in the first piece of code, four tests must be made before the names, Bill and Mary, can be put out. 11.6 Label variables also have the feature that they can identify ranges of an integer for which certain actions should be taken. The two pieces of code below produce the same effect. int i int i label A(5) label A(5) loop for i = 1 to 5 loop for i = 1 to 5 goto A(i) goto A(i) A(1): putc Girl A(1): putc Girl goto B goto B A(2): A(2): putc Boy A(3): goto B A(4): putc Boy A(5): putc Girl goto B goto B A(5): putc Girl B: repeat goto B B: repeat In the second piece of code, the labels A(3) and A(4) don't exist, so the program jumps to the label A(2). The rule is that if a label doesn't exist, jump to the next lowest one that does. ────────────────────────────────────────────────────────────────── ┌───────────────────┐ │ 12. Relations │ └───────────────────┘ 12.1 A relation is used to describe a test condition. Examples of relations are: x < y, x = y, x > y. The condition described by a relation is either true or false, depending on the values of the variables at run time. A relation has the following structure: <entity> <relational operator> <entity> The <entity> may be a variable, a literal, a non-string function, a non-string expression, or (right hand side) a set of characters (numbers between 0 and 255). The entities on each side of a relation must be of the same variable type. Strings 12.2 For strings there are eight relational operators: = equals > greater than < less than <> not equal >= greater than or equal to <= less than or equal to in entirely contained in (a set) con contains (string) or containing a member of (set) 12.3 The first six operators evaluate the two strings of a relation, byte by byte, treating each byte arithmetically. If a difference is found, a definitive result can be given for operators two through six. Only if all bytes are found to be the same, and the lengths also the same, are the strings determined to be equal. If the lengths are unequal, but the strings are identical up to the length of the shorter string, the longer string is determined to be greater than the shorter string. 12.4 The "in" operator requires that the right hand side be a set. The left hand side must be a string or a substring. The relation is evaluated as true if and only if all characters in the string or substring are members of the set. If the left hand side is a single character string (string with length one), the relation is true if that character is a member of the set. 12.5 The "con" operator may have a string, a substring, or set on the right hand side. The left hand side may be a string or a substring. If the right hand side is a string or substring, the relation is true if the left hand string is equal to or contains the string or substring on the right. If the right hand side is a set, the relation is true if any byte in the left hand entity is in the set. 12.6 The con operator has one other property. Zbex has five special integer variables, trp, sze, rem, mpt, and sub, which are set by various runtime operations. Two of them, mpt (match point) and sub (subscript), are given new values when the "con" relation is true. The value assigned to mpt is the position in the left hand string or substring where the truthful result was first detected. The value assinged to sub is the subscript in the left hand string variable where the truthful result was first detected. The difference between "position" (value of mpt) and "subscript" (value of sub) is illustrated by the following example: str test.5 test = "abcde" if test{3..5} con "e" putc mpt = ~mpt sub = ~sub end In this example, the "con" relation is true and mpt is set to 3, the point in the sub-string "cde" where the "e" was found. sub is set to 5, which is the subscript in the variable test "abcde" where the "e" occurs. 12.7 In this section, reference was made to an entity called a set. A set is sub-set of the numbers 0 through 255, i.e., the possible values of a byte. For this reason, we also refer to a set as a character set. Below are some examples of sets. 1. ['a'..'z'] the small alphabet 2. ['A'..'Z','a'..'z'] the large and small alphabet 3. ['0'..'9'] the digits 4. [48..57] the values 48 to 57, which are the ASCII values for the numbers 0 to 9. Sets 3. and 4. are the same. 5. [32] the value 32. 6. [' '] the space character, ASCII 32. Sets 5. and 6. are the same. 7. ['A'..'z'] all values between 'A' = 65 and 'z' = 122. If you don't know or can't remember the ASCII values for the various characters, don't worry about it. Few people have reason to remember these values. When I want to be reminded of the ASCII values for various characters, I just write and run the following program: str a.1 int i loop getc a i = ors(a) putc ~a = ASCII ~i repeat run The expression ors(a) is a function which converts the string a to an integer. We will discuss functions in the next section. Bit strings, Integers, Real numbers 12.8 For the non-string data types there are six relational operators. = equals > greater than < less than <> not equal >= greater than or equal to <= less than or equal to For bit strings, these operators behave in the same manner as with strings. For the numerical data types, these operators behave in the usual manner. The operators are sign sensitive, i.e., a negative number closer to zero is larger than a negative number farther from zero. ────────────────────────────────────────────────────────────────── ┌───────────────────┐ │ 13. Functions │ └───────────────────┘ 13.1 The Zbex language includes a number of functions. The purpose of functions is to facilitate the writing of Zbex programs and to increase the speed of these programs. Functions can be used in assignment statements any place a normal variable can be used. Functions are classified by the data type of their output. Non-string and non-bit string functions can appear in relations. Functions whose output is an integer Function Output ────────── ──────────────────────────────── rnd(int) deliver a random number between 0 and <input> not(int) ones complement of <input> abs(int) absolute value of <input> and(int,int) for input (x,y), output = x & y ior(int,int) for input (x,y), output = x | y xor(int,int) for input (x,y), output = x exclusive or y bit(int,int) for input (x,y), output = bit x of y (0 is low order) shr(int,int) for input (x,y), output = shift x right y bits shl(int,int) for input (x,y), output = shift x left y bits tst(table) output = number of entries in the table tst also resets the sequential tget tdx(table,str) output = index number for key (second input) or 0 if the given key is not present fix(real) integer value of real number input len(str) length of input string int(str) integer value of string of digits, including the unitary plus or minus sign. The function skips leading blanks and also sets the sub variable to subscript of terminating (non digit) byte or to len(str) + 1 ors(str) integer value of bit pattern in first 4 (or less) bytes of the string input len(bstr) byte length of padded bit string input bln(bstr) bit length of bit string input Functions whose output is a real number Function Output ────────── ──────────────────────────────── flt(int) convert integer value to real number rnd(real) deliver a random number between 0 and <input> abs(real) absolute value of <input> dec(real) decimal part of <input> sin(real) for input (x), output = sin(x) cos(real) for input (x), output = cos(x) tan(real) for input (x), output = tan(x) ars(real) for input (x), output = arcsin(x) arc(real) for input (x), output = arccos(x) art(real) for input (x), output = arctan(x) exx(real) for input (x), output = (e to the x power) lnx(real) for input (x), output = (log base e of x) sqt(real) for input (x), output = square root of x pow(real,real) for input (x,y), output = x to the y power (x,y > 0) flt(str) real value of string of digits, including the unitary plus or minus sign. The function skips leading blanks and also sets the sub variable to subscript of terminating (non digit) byte or to len(str) + 1 Functions whose output is a string Function Output ────────── ──────────────────────────────── pad(int) add blanks to the end of the current right hand string, up to a length of <input> number. zpd(int) add nulls (the zero byte) to the end of the current right hand string, up to a length of <input> number. chr(int) single string character with a bit pattern that matches the lower eight bits of <input> ch2(int) two character string with a bit pattern that matches the lower sixteen bits of <input> ch4(int) four character string with a bit pattern that matches the <input> oct(int) ASCII octal representation of <input> chs(int) ASCII decimal representation of <input> including the unitary minus sign hex(int) ASCII hexidecimal representation of <input> ch8(real) eight character string with a bit pattern that matches the <input> chs(real,int) ASCII decimal representation of <real input> with <int input> digits to right of decimal. Includes unitary minus sign chx(real,int) ASCII floating point representation of <real input> with <int input> digits to right of decimal. Includes unitary minus sign trm(str) input string without the trailing blanks mrt(str) input string without the leading blanks lcs(str) input string with all letters converted to lower case ucs(str) input string with all letters converted to upper case rev(str) string with characters in the reverse order from the input string rpl(str,s(n,2)) create an output string from the first input string in following way: Working from left to right in the input string, search the string array s(i,1) (i = 1,...,n) for a match in the input string. If a match is found (at i), place s(i,2) in the output string and advance the test point in the input string by len(s(i,1)). If no match is found, copy the current byte to the output string and advance the test point one byte. dup(str,int) input string duplicated <int input> number of times txt(s1,bs,int) output string = s1{m..n-1} where m (>= int) is the subscript of first byte of the input string (at or beyond <int input>) which is not in the bit string set bs, and n (> m) is the subscript of the first byte of the input string (beyond m) which is in the bit string set bs. <int input> is set to the value n by this function. Essentially, characters in the bit string set bs are used to sub-divide or parse the input string. The function is designed for repeated calls until the input string is completely parsed. The format for specifying the bit string set bs is: set(bstr) where bstr is a bit string. txt(s1,bs) same as previous function, except the special variable mpt is used in place of the third input. txt(s1,[ ],int) same as the first txt function, except the set [ ] is used instead of the bit string set. txt(s1,[ ]) same as the second txt function, except the set [ ] is used instead of the bit string set. cby(bstr) create output string with the same bit pattern as the input bit string (padded to the byte boundary with zeros) upk(bstr,s2) construct a string from the first <input> in the following way: 1 maps to s2{1} or 'x', 0 maps to s2{2} or ' '. upk(bstr) construct a string from the first <input> in the following way: 1 maps to 'x', 0 maps ' '. Functions whose output is a bit string Function Output ────────── ──────────────────────────────── npd(int) add ones to the end of the current right hand bit-string, up to a length of <input> number. zpd(int) add zeros to the end of the current right hand bit-string, up to a length of <input> number. cbi(str) create output bit string with the same bit pattern as the input string pak(s1,s2) construct a bit string from the first input string as follows: for each byte, if byte = s2{1} put in a one, otherwise put in a zero. pak(str) construct a bit string from the input string as follows: for each byte, if byte = 'x', put in a one, otherwise put in a zero. trm(bstr) input bit string without the trailing zeros mrt(bstr) input bit string without the leading zeros rev(bstr) bit string with bits in the reverse order from the input bit string cmp(bstr) 1's complement of input bit string dup(bstr,int) input bit string duplicated <int input> number of times bnd(bstr,bstr) output = intersection of two inputs bor(bstr,bstr) output = union of two inputs Function whose output is a set Function Output ────────── ──────────────────────────────── set(bstr) create a set in the following way: Pad the input bit string with zeros up to a length of 256. For each of the 256 bits in the bit string, if the bit is a one, add the number of the position to the set; if the bit is a zero, do not add the number of the position to the set. 13.2 There are thirteen sets of ambiguous functions. In each case, there is a way for the compiler to distinguish which function is being called. 1. rnd(int) --> int distinguished by rnd(real) --> real output type 2. abs(int) --> int distinguished by abs(real) --> real output type 3. len(str) --> int distinguished by len(bstr) --> int input variable type. 4. flt(int) --> real distinguished by flt(str) --> real input variable type. 5. zpd(int) --> str distinguish by zpd(int) --> bstr output type 6. chs(int) --> str distinguish by number chs(real,int) --> str of arguments 7. trm(str) --> str distinguish by trm(bstr) --> bstr output type 8. mrt(str) --> str distinguish by mrt(bstr) --> bstr output type 9. rev(str) --> str distinguish by rev(bstr) --> bstr output type 10. dup(str) --> str distinguish by dup(bstr) --> bstr output type 11. txt(s1,bs,int) --> str distinguish by type txt(s1,bs) --> str of second argument txt(s1,[ ],int) --> str and by presence txt(s1,[ ]) --> str of third argument 12. upk(bstr,s2) --> str distinguish by number upk(bstr) --> str of arguments 13. upk(s1,s2) --> str distinguish by number upk(str) --> str of arguments 13.3 We include some examples of how various functions work. The focus is mainly on those functions whose operation may not be clear from the description. 1. The tst(table) function. This function reports on the number of enteries in the table, and resets the counter for the sequentail version of the tget command. To demonstrate this function, we must fill a table with some items and use the sequential version of tget to retrieve them. Program ═════════════════════ str a.10,b.10 int i table X(1000) loop for i = 1 to 10 /* create 10 entries in table a = "Key " // chs(i) /* str a will contain the "key" tput [X,a] Item ~i repeat loop for i = 1 to 3 /* get first 3 entries tget [X] a b putc ~a ~b repeat i = tst(X) putc Number of entries = ~i loop for i = 1 to 3 /* check to see that sequential tget [X] a b /* counter has been reset putc ~a ~b repeat run Execution ═════════════════════════════════════ ** S=18, P=79, L=253, M=1418 ** Key 1 Item 1 Key 2 Item 2 Key 3 Item 3 Number of entries = 10 Key 1 Item 1 Key 2 Item 2 Key 3 Item 3 Ready for program 2. The tdx(table,str) function. This function provides the index number in a table for a given "key". The number is 0 if there is no such key in the table. To demonstrate this function, we must fill a table with some key/record pairs and then use the tdx function to get their index numbers. Program ═════════════════════ str a.10,b.10 int i,j table X(1000) loop for i = 1 to 10 /* create 10 entries in table a = "Key " // chs(i) /* str a will contain the "key" tput [X,a] Item ~i repeat loop for i = 1 to 3 /* get index for first 3 entries a = "Key " // chs(i) j = tdx(X,a) putc key = ~a index = .w4 ~j ... tget [X,j] b /* retrieve record via index putc record = ~b repeat run Execution ═════════════════════════════════════ ** S=15, P=75, L=255, M=1418 ** key = Key 1 index = 313 record = Item 1 key = Key 2 index = 58 record = Item 2 key = Key 3 index = 187 record = Item 3 Ready for program 3. The fix(real) function. The reason we need this function is that Zbex does not automatically convert real numbers to integers. Program ═════════════════════ int i real x x = 456.789 i = fix(x) putc real input = ~x integer output = ~i run Execution ═════════════════════════════════════ ** S=6, P=28, L=236, M=410 ** real input = 456.79 integer output = 456 Ready for program 4. The len(str) function. When used on the right hand side of an assignment statement, this function gives the length of the string argument. What makes this function unusual is that it can also be used on the left hand side of an assignment statement to set the length of a string. This is particularly valuable when we use a string as a buffer for the read instruction, since the size of the block read = the length of the string. Program ═════════════════════ str a.80 int i a = "For the last time, Mr. Smee, take the princess home!" i = len(a) putc Length of string a is ~i len(a) = 28 i = len(a) putc New length of string a is ~i putc String a = "~a " run Execution ═════════════════════════════════════ ** S=10, P=59, L=255, M=431 ** Length of string a is 52 New length of string a is 28 String a = "For the last time, Mr. Smee," Ready for program 5. The int(str) function. This function reads a group of digits in a string and returns their value as an integer. The setting of the special variable sub to the subscript of the the terminating (non digit) byte in the string allows this function to be called again to get the next number in the string. Program ═════════════════════ str a.80 int i a = "1000 500 -39 465 678687" putc Getting numbers from a string putc a = "~a " putc Numbers putc ─────── sub = 0 loop while sub < len(a) i = int(a{sub+1..}) putc .w7 ~i .w2t20 (terminating subscript = ~sub ) repeat run Execution ═════════════════════════════════════ ** S=13, P=72, L=248, M=431 ** Getting numbers from a string a = "1000 500 -39 465 678687" Numbers ─────── 1000 (terminating subscript = 5) 500 (terminating subscript = 10) -39 (terminating subscript = 19) 465 (terminating subscript = 25) 678687 (terminating subscript = 32) Ready for program 6. The ors(str) function. This function reads the first four bytes of the string argument (less if the string is shorter) and creates an integer value from the bit pattern. Program ═════════════════════ str a.4 int i a = "." i = ors(a) putc a = "~a " .t13 i = ~i .t30x (hex ~i ) a = ".1" i = ors(a) putc a = "~a " .t13 i = ~i .t30x (hex ~i ) a = ".123" i = ors(a) putc a = "~a " .t13 i = ~i .t30x (hex ~i ) run Execution ═════════════════════════════════════ ** S=12, P=95, L=249, M=412 ** a = "." i = 46 (hex 2e) a = ".1" i = 11825 (hex 2e31) a = ".123" i = 774976051 (hex 2e313233) Ready for program 7. The pad(int) function. This function adds blanks to the end of the string on the right hand side of an assignment statement up to the length specified by the integer argument. If the string is already longer than the specified length, nothing is done. Program ═════════════════════ str a.80,b.80 int i a = "This is a short string" b = "This is a slightly longgggerrrrrr string" putc a = "~a " a = a // pad(30) putc a = "~a " putc b = "~b " b = b // pad(30) putc b = "~b " run Execution ═════════════════════════════════════ ** S=11, P=54, L=262, M=452 ** a = "This is a short string" a = "This is a short string " b = "This is a slightly longgggerrrrrr string" b = "This is a slightly longgggerrrrrr string" Ready for program 8. The ch4(int) function. This function is the opposite of the ors function. It creates a four byte string from the bit pattern of the integer argument. Program ═════════════════════ str a.4 int i i = 0x31323334 /* this is hexidecimal notation a = ch4(i) putc a = "~a " run Execution ═════════════════════════════════════ ** S=6, P=19, L=237, M=412 ** a = "1234" Ready for program 9. The chs(int) function. This function constructs a string which is the ASCII decimal representation of the integer argument. We illustrate also the hex(int) function here. Program ═════════════════════ str a.80,b.80 int i a = "" b = "" loop for i = 500 to -300 step -100 a = a // chs(i) // " " b = b // hex(i) // " " repeat putc a = "~a " putc b = "~b " run Execution ═════════════════════════════════════ ** S=11, P=55, L=247, M=452 ** a = "500 400 300 200 100 0 -100 -200 -300 " b = "1f4 190 12c c8 64 0 ffffff9c ffffff38 fffffed4 " Ready for program 10. The rpl(str,s(n,2)) function. This somewhat unwieldy function is intended to facilitate the translation of certain string patterns into alternate string patterns for large blocks of text at a time. rpl is short for replace. The second argument is an array of patterns and their replacements. Program ═════════════════════ str a.500, b.500, c.10(1,2) set up initial string for testing a = "The purpose of this function is to facilitateCRthe" a = a // " translation of certain string patterns intoCRother" a = a // " string patterns. In this example, we willCRchange" a = a // " a code for carriage return into the escapeCR" a = a // "sequences which will generate real carriageCR" a = a // "returns.CR" set up conversion matrix c(1,1) = "CR" c(1,2) = chr(27) // "S" // chr(27) // "Y" do the translation b = rpl(a,c) display results putc ~b run Execution ═════════════════════════════════════ The purpose of this function is to facilitate the translation of certain string patterns into other string patterns. In this example, we will change a code for carriage return into the escape sequences which will generate real carriage returns. Ready for program 11. The txt(s1,bs,int) function. This is the most complicated of the four forms of the txt function. The purpose of this function is to allow the rapid parsing of strings, based on a set of bytes one might call "delimiter bytes." In this version of the function, the set is determined by a bit string. Program ═════════════════════ str a.100, b.100 bstr pset.256 int i a = "Let us, please, ignor (i.e., disregard) all" a = a // " non-alpha characters in our parse!! O.K.?" pset = npd(65) // zpd(91) // npd(97) // zpd(123) // npd(256) i = 0 loop while i < len(a) b = txt(a, set(pset), i) b = txt(a, [0..64,91..96,122..255], i) will also work if b <> "" putc ~b end repeat run Execution ═════════════════════════════════════ ** S=14, P=52, L=283, M=471 ** Let us please ignor i e disregard all non alpha characters in our parse O K Ready for program 12. The upk(bstr,s2) function. This function creates a string with two types of characters. The pattern matches that of the bit string argument. Program ═════════════════════ str a.50,b.2 bstr pattern.50(10) int i b = "▒█" pattern(1) = "0001111111000" pattern(2) = "0111111111110" pattern(3) = "1110000000111" pattern(4) = "1100110110011" pattern(5) = "1100001000011" pattern(6) = "1100100010011" pattern(7) = "0110011100110" pattern(8) = "0111000001110" pattern(9) = "0001111111000" loop for i = 1 to 9 a = upk(pattern(i), b) putc ~a repeat run Execution ═════════════════════════════════════ ** S=18, P=90, L=267, M=456 ** ███▒▒▒▒▒▒▒███ █▒▒▒▒▒▒▒▒▒▒▒█ ▒▒▒███████▒▒▒ ▒▒██▒▒█▒▒██▒▒ ▒▒████▒████▒▒ ▒▒██▒███▒██▒▒ █▒▒██▒▒▒██▒▒█ █▒▒▒█████▒▒▒█ ███▒▒▒▒▒▒▒███ Ready for program ────────────────────────────────────────────────────────────────── ┌───────────────────┐ │ 14. Procedures │ └───────────────────┘ 14.1 A procedure is a section of code which can be entered (called) from different points in a program and which, when completed, will return control to the calling point. Procedures may call other procedures, but Zbex is not designed for recursive procedure calls (that is when a procedure calls itself). The codes for procedures must come after the main program. 14.2 A procedure is identified by the procedure statement. A procedure is called using the perform statement. Return from a procedure is accomplished with the return statement. Example: Program ═════════════════════ int i,j loop for i = 1 to 5 perform square putc ~i ~j repeat stop procedure square j = i * i return run Execution ═════════════════════════════════════ ** S=10, P=29, L=235, M=410 ** 1 1 2 4 3 9 4 16 5 25 Ready for program 14.3 It is possible to declare variables inside a procedure. Variables so declared can be referenced only within the procedure. If a variable of the same name was declared in the main program, it will be masked (unaccessable) inside the procedure. Example, using the variable i: Program ═════════════════════ int i,j,k loop for i = 1 to 5 j = i perform factorial putc ~i ~k repeat stop procedure factorial int i k = 1 loop for i = 1 to j k = k * i repeat return run Execution ═════════════════════════════════════ ** S=15, P=41, L=239, M=410 ** 1 1 2 2 3 6 4 24 5 120 Ready for program 14.4 It is possible to pass data to a procedure and for a procedure to pass data back to the calling point. The buffers for this must be included in the procedure statement, and the data types for the buffers must be declared inside the procedure. Values must be loaded into the buffer at the time the procedure is called. The actual transfer of data is handled by the getvalue and passback statements inside the procedure. Example: Program ═════════════════════ int i,j loop for i = 6 to 10 perform factorial (i,j) putc .w2 ~i .t4w8 ~j repeat stop procedure factorial (a,b) int a,b,i getvalue a b = 1 loop for i = 1 to a b = b * i repeat passback b return run Execution ═════════════════════════════════════ ** S=16, P=53, L=241, M=410 ** 6 720 7 5040 8 40320 9 362880 10 3628800 Ready for program 14.5 There are times when you will want to return directly to a point in the main program, irrespective of how many levels of procedure calls you have made. An example of this would be a language compiler. If you are processing a line of text and a procedure within a procedure within a procedure identifies a syntax error, you would not want to trace your way back to the main program through these procedures; instead, you would want to jump directly to the error handling portion of the program with a flag telling that portion what went wrong. Zbex provides you a way to do this using the special trp variable and the special trap label. If you include an integer number with a return statement, the record of procedure calls will be erased and control will pass directly to the trap label in the main program. The special integer variable trp will be assigned the value attached to the return statement. Example: Program ═════════════════════ int i trp = 0 trap: i = 0 perform pro1 stop procedure pro1 putc pro1 ... i = i + 1 if i > trp putc return 1 end perform pro2 putc returning from pro1 return procedure pro2 putc pro2 ... i = i + 1 if i > trp putc return 2 end perform pro3 putc returning from pro2 return procedure pro3 putc pro3 putc returning from pro3 return run Execution ═════════════════════════════════════ ** S=31, P=83, L=233, M=410 ** pro1 pro1 pro2 pro1 pro2 pro3 returning from pro3 returning from pro2 returning from pro1 Ready for program 14.6 There are only two ways to interrupt an Zbex program once it has been started. One of these is by pressing the <ctrl> Break key (the other is by responding to a getc with two exclaimation points (!!)). On occasion you may want <ctrl> Break to actually do something in your Zbex program. For example, if your program is writing to the screen, and you want it to stop, you must push <ctrl> Break. In some cases, you may not want your Zbex program to halt, you may simply want to get control of it. Zbex provides you a means for doing this. It is a special procedure called break. If you include a procedure with this name, control will automatically pass to it when you press the <ctrl> Break key. A normal return from this procedure will put you back into your Zbex program exactly at the point where <ctrl> Break was pressed. Example: Program ═════════════════════ int i,j loop for i = 1 to 100000 loop for j = 1 to 300000 /* (for a delay) repeat putc ~i repeat stop procedure break getc return run Execution ═════════════════════════════════════ ** S=11, P=26, L=235, M=410 ** 1 2 3 (here, <ctrl> Break was pressed and a blank line entered) 4 5 !! (here, <ctrl> Break was pressed and !! was entered) Ready for program 14.7 There is an important precaution you must observe with the procedure break. You need to provide some mechanism for terminating the program inside the procedure, or else you may not be able to terminate your program at all (except by exiting Dmuse)! The program below, once started, cannot be stopped, except by exiting Dmuse. int i,j loop i = i + 1 putc ~i repeat stop procedure break return run ────────────────────────────────────────────────────────────────── ┌───────────────────┐ │ 15. Tables │ └───────────────────┘ 15.1 Zbex provides you with a special variable type called a table. A table is designed to hold records, much like a file. You may put records into a table using an index number or using string called a key. You may retrieve records from the table the same way. There is also a way to access sequentially all records and their keys. Tables must be declared using the table declaration statement. All tables msut be declared as one dimensional arrays. The size should about 50% larger than you think you need. This is because records stored and retrieve with a key use the hash addressing technique. Records stored in a table may be up to 1000 bytes long. You may retrieve a record, increase its size, and put it back in the table without worrying about writing over some other record. Memory for records in a table is allocated dynamically at run time. Tables are useful in many applications. If you want to process records at random out of a file, the best way to do this is to read the entire file into a table. If you want to count words from a large document to determine frequency ratios, the best way to do this is to use the words as keys and store the counts as records. Any application that calls for random access either by index or by key will be well served by the use of tables. 15.2 There are three instructions that address tables: tget, tput, and treset. 15.3 1. tget There are three formats to the tget instruction. The first format has the table name in square brackets, followed by strings for a key and a record. This format is used for sequential reading of the table. The second and third formats differ only in that the second parameter inside the square brackets is a string (key) in one case and an integer expression (index) in the other. These instructions are used to retrieve records from the table by key or by index. The format for the input variable(s) is the same as for getc. In the first format, tget is used to sequentially read key/record pairs from the table. The purpose of this instruction is to allow the table to be read as a whole. At run time, the sequential pointer is set to the first record. It can be reset to the first record by calling the tst function. Each execution of tget will retrieve the next key/record entry in the table. An attempt to retrieve beyond the end of the table will result in a run-time error. All key/record pairs will be retrieved, including those which have null keys and null records. In the second format, tget is used to retrieve a record from its key. The key is the second parameter inside the square brackets. If the key does not exist in the table, the null string is returned. In the third format, tget is used to retrieve a record from its index in the table. Any index between 1 and the maximum size of the table is considered valid. If the given index has no corresponding pointer in the index table, i.e., no record has been entered with this index, the null string will be returned. An invalid index will result in a run-time error. 15.4 2. tput There are two possible formats to the tput instruction. These formats are roughly the equivalent of the tget formats two and three. tput is used to store records in a table by key or by index. The format of the output field of the tput instruction is the same as for the putc and putf instructions. You may put out either variables or ASCII test. Variables must be preceded by a tilda sign (~) and followed with a space (see the section on input and output). In the first format, tput is used to store data in a table record using a key string as the access mechanism. If the key does not already exist in the table, it is created and the run-time size of the table is incremented. If in so doing, the run-time size exceeds 90% of the maximum size, a run-time error will result. This is because hash tables become very inefficient when their size increases beyond 90% of the allocated space. The data to be stored may include the null string. From the point of view of accessing this record by a key, this will have the effect of removing the key/record group from the table. The key/record group is still accessable using the sequential version of tget. In the second format, tput is used to store data in a table record using an index as the access mechanism. If the index is less than one or greater than the maximum size of the table, a run-time error will result. If a key does not exist for this index, the null key will be written and the run-time size will be incremented. 15.5 3. treset The treset instruction needs only the name of the table specified in square brackets. This instruction allows a table to be reset to empty, so that it may be reused by the program. This is useful in the case where data is being collected multiple times in the same program session. ────────────────────────────────────────────────────────────────── ┌────────────────────────────┐ │ 16. Handling directories │ └────────────────────────────┘ 16.1 Zbex gives you two commands which relate to directories: getdir and createdir. In addition, you can read the contents of a directory by opening it as a file. 16.2 getdir = get the path of the current directory. The getdir statement will provide you with the path of the current directory. There is no way with Zbex to change the current directory. The following example will open the current directory, read its contents, and display them on the screen. Program ═════════════════════ str curdir.80,rec.80 getdir curdir open [1,1] curdir loop getf [1] rec putc ~rec repeat run Execution ═════════════════════════════════════ ** S=8, P=25, L=239, M=452 ** DISP <DIR> 7-18-95 10:12p FORMATS <DIR> 8-10-95 11:01p MKSLURS <DIR> 11-01-95 10:08p SPECS <DIR> 7-18-95 10:11p COMPRESS OLD 59427 7-05-95 3:12p DECOMP OLD 26037 7-05-95 3:12p BUG Z 197 7-05-95 3:12p EXPAND Z 28179 10-08-95 12:18a MAKECFT Z 66767 10-09-95 7:22a 9 Files(s) Ready for program 16.3 createdir = create a new directory. The createdir statement lets you create a new directory. The path to the new directory must already exist (you can create it, one sub-directory at a time). Zbex does not provide you with a means of removing directories (or removing files, either). The following example will create a new directory in the current directory. Program ═════════════════════ str newdir.80,curdir.80 str rec.80,temp.80 getdir curdir B: putc Name of new directory? getc newdir temp = ucs(newdir) /* for comparing with current names Make sure that this directory doesn't already exist open [1,1] curdir loop getf [1] rec if rec{1} = " " /* no more entries in directory goto A end if rec{18} = ">" /* looking for sub-directories if rec con " " rec = rec{1,mpt-1} /* keep only the name end if rec = temp putc This directory already exists close [1] /* must close [1] so we can open it an goto B end end repeat A: close [1] Directory doesn't exist; try to create it. newdir = curdir // "/" // newdir createdir newdir putc ~newdir created run Execution ═════════════════════════════════════ Name of new directory? specs This directory already exists Name of new directory? rivet C:\RELEASE\INTERNET/rivet created Ready for program ────────────────────────────────────────────────────────────────── ┌───────────────────────────────────┐ │ 17. Special variables and labels │ └───────────────────────────────────┘ 17.1 Some of the material covered in this section has also been presented in other sections. This is because all special variables and special labels are related to some aspect of the operation of Zbex. 17.2 There are six special variables: err, rem, mpt, sub, sze, and trp. These variables are integer type and are automatically included in your program (with declaration). 17.3 err This variable takes on meaning only when bit 12 of the zoperation flag is set. (see Section 18.3 on how to do this using the setflag instruction). Under default operation (bit 12 of zoperation flag is clear = 0), when a Zbex open instruction fails for any reason, Zbex sends a prompt to the user asking for a new (different) file name. The program is suspended until this supplied. Sometimes, however, the user does not welcome this interruption; if the file or directory can't be opened, the user wants the program to ignor the open request and move on. This will happen if bit 12 of the zoperation flag is set = 1.. In this case, how does the user's program know that open has failed, and why? Answer, Zbex sets the special variable err to one of the following values: 0 = open was successful 1 = cannot expand to full file name 2 = file already open somewhere else 3 = cannot open the file at all 4 = not a file or a directory 5 = unable to read directory 6 = read access on file is denied 7 = cannot open file for writing 3 = cannot open the file at all 8 = writing allowed only on regular files 9 = read/write access on file is denied 10 = write access on file is denied 11 = cannot create new file Program control moves on to the next Zbex instruction, which almost certainly would be to test the value of err. 17.4 rem This variable is assigned a value every time an integer divide is performed. If more than one integer divide is performed in a statement, the value of rem will be set by last integer divide to be performed by the statement. The sign of rem is always the sign of the dividend. Example: Program ═════════════════════ int q q = 13 / 9 putc for 13 / 9, quotient = ~q rem = ~rem q = -13 / 9 putc for -13 / 9, quotient = ~q rem = ~rem q = 13 / -9 putc for 13 / -9, quotient = ~q rem = ~rem q = -13 / -9 putc for -13 / -9, quotient = ~q rem = ~rem run Execution ═════════════════════════════════════ ** S=11, P=98, L=237, M=410 ** for 13 / 9, quotient = 1 rem = 4 for -13 / 9, quotient = -1 rem = -4 for 13 / -9, quotient = -1 rem = 4 for -13 / -9, quotient = 1 rem = -4 Ready for program 17.5 mpt This variable is affected by two types of statements. (1) If the "con" operator in a relation is successful, mpt is assigned the number which is the position in the left hand string where the "con" operate found a match. (2) When the txt function appears with only two of its three required variables, mpt is assumed to be the third variable. If the txt function is successful in parsing a sub-string from the source string, mpt will be set to the subscript of the parsing byte (see section on functions). Example: Program ═════════════════════ str a.10,b.10 putc Testing mpt with con putc ──────────────────── a = "Elmer Fudd" if a{3..5} con "er" putc "er" occurs at position ~mpt in "~a{3..5} " end putc putc Testing mpt in txt putc ────────────────── mpt = 1 putc mpt = ~mpt b = txt(a,[32]) putc First word = ~b putc mpt = ~mpt run Execution ═════════════════════════════════════ ** S=11, P=66, L=255, M=418 ** Testing mpt with con ──────────────────── "er" occurs at position 2 in "mer" Testing mpt in txt ────────────────── mpt = 1 First word = Elmer mpt = 6 Ready for program 17.6 sub This variable is affected by two types of statements. (1) If the "con" operator in a relation is successful, sub is assigned the subscript in the left hand (string) variable where the "con" operate found a match. match. (2) The int(str) and flt(str) functions assign to sub the subscript of the first byte which is not part of the integer format (or real number format). Examnple: Program ═════════════════════ str a.20 int i real x putc Testing sub with con putc ──────────────────── a = "Elmer Fudd" if a{3..5} con "er" putc "er" occurs at subscript ~sub in "~a " end putc putc Testing sub with int(str) and flt(str) putc ────────────────────────────────────── putc a = " 234.890; " a = " 234.890; " sub = 1 putc sub = ~sub i = int(a) putc Integer value = ~i putc sub = ~sub putc sub = 1 putc sub = ~sub x = flt(a) putc Real value = ~x putc sub = ~sub run Execution ═════════════════════════════════════ ** S=26, P=152, L=255, M=416 ** Testing sub with con ──────────────────── "er" occurs at subscript 4 in "Elmer Fudd" Testing sub with int(str) and flt(str) ────────────────────────────────────── a = " 234.890; " sub = 1 Integer value = 234 sub = 6 sub = 1 Real value = 234.89 sub = 10 Ready for program 17.7 sze This variable is assigned a value when you open a file for type 5 access (random read/write). The value is the number of bytes in the file being opened. You will need this number in order to avoid reading beyond the end of the file. 17.8 trp This variable is assigned a value when a return statement with a literal integer is encountered. The value is the value of the integer. 17.9 There are eleven special labels; trap, brk, and eof1 to eof9. The trap and brk labels should be used only in the main part of your program, i.e., not in procedures. 17.10 trap: The trap label, if used, marks the location in the main program where control is transferred when an abnormal return is encountered in a procedure. An abnormal return from a procedure clears the entire procedure return stack and assigns a value to the special integer variable trp. 17.11 brk: The brk label, if used, marks the location in the main program where control is transferred when the break key is pressed. If the break procedure is present, this will override the transfer to brk. If neither the brk lable nor the break procedure is present, pressing the break key will terminate the program. Examples: (1) with brk only. Program ═════════════════════ loop repeat brk: putc Break pressed run Execution ═════════════════════════════════════ ** S=5, P=11, L=231, M=410 ** Break pressed Ready for program (2) with brk and the break procedure Program ═════════════════════ loop repeat brk: putc Break pressed stop procedure break putc Break pressed, what now? getc return run Execution ═════════════════════════════════════ ** S=10, P=23, L=231, M=410 ** Break pressed, what now? (blank line entered) Break pressed, what now? !! Ready for program 17.12 eof1: The eof1 label, if used, may appear anywhere in a program, including inside a procedure. The eof1 location is the entry point for program control following an attempt to getf a record sequencially beyond the last record in a file opened with a tag of [1]. The special labels, eof2 to eof9 work in a similar fashion. 17.13 Clearly, some caution must be exercised in the use of the special labels. Since these labels may be placed inside loops with counters, it is possible to jump into such a loop without having set up the counter variable or its limits. This could lead to unpredictable run time results. More seriously, the eof labels can be placed in procedures which may not be properly called at run time. The following program will bomb the the interpreter, because the return from the procedure "death" cannot be properly executed. Program ═════════════════════ open [1,1] "<an existing file>" loop getf [1] repeat putc Congratulations! You've done the impossible. stop * procedure death eof1: return ────────────────────────────────────────────────────────────────── ┌──────────────────────────────────┐ │ 18. Instructions for debugging │ └──────────────────────────────────┘ 18.1 The Zbex language provides several tools for debugging programs. When you write a program, there are three levels of success that you need to achieve. The first is to have your program compile. This means following the proper syntax of the language. Zbex includes an extensive set of compiler time error messages which not only identify errors in your code but also try to explain what you need to do to fix the code. The second level of success is to have your program run. The most common run time error in Zbex programs is a subscript error. The Zbex interpreter knows the size of all of your strings and arrays, and if you try to address memory outside of this range, the interpreter will stop the execution of your program and tell you the line number in your program where the error occurred and what the offending subscript values were. The third level of success is to get your program to do what you want it to do. For large programs, this is the most difficult and most time consuming level to achieve. Most of Zbex's debugging tools are designed to help you solve this problem. 18.2 1. dputc and dputp. These instructions are identical to putc and putp except that the line number where they sit in your program is also displayed (printed out). This way, you can check on the value of certain variables at different points in your program and always be able to tell where those points are. Example: Program ═════════════════════ 1 int i 2 3 loop for i = 1 to 3 4 i = i + 6 5 repeat 6 dputc i = ~i 7 run Execution ═════════════════════════════════════ ** S=6, P=20, L=233, M=410 ** T0006 i = 7 Ready for program 18.3 2. The behavior of Zbex in certain situations is determined by a set of flags. The state of these flags can be changed using the setflag instruction. This instruction has two formats. Format 1: setflag x[xxxxxxxxxxxxxxxx] (x = 0 or 1) Format 2: setflag x,y (x = bit number, y = 0 or 1) In format 1, from 1 to 12 bits are specified in the second field of the instruction. Format 1 can be used to set the state of all twelve flags at one time. In format 2, you supply a bit number and its new setting. With format 2, you can set the state on only one flag at a time. The twelve flags control the following aspects of Zbex operation. The default setting of all flags is 0 (no action). Bit Num Zbex behavior when bit is 1 ─────── ────────────────────────────────────────────────────── 1 Give warning on overflow on integer multiply 2 Give warning on overflow on conversion of ASCII string to integer 3 Give warning on attempt to divide by zero 4 Give warning on overflow on floating point number to integer conversion 5 I/O flag -- convert out-of-bounds fixed decimal floating point representation to floating decimal floating point representation 6 Give warning on attempt to read/write partially outside of bounds of a file (read/writing totally outside is cause for termination) 7 Give warning when LHS of string statement with subscripts has a different length than the RHS 8 Give warning when the argument of a real function is outside the proper domain 9 Give warning when string subscripts are out-of-bounds in relational statements 10 Suppress warnings for writing more than 100,000 records or more than 6 MB in a file 11 Give warning when the initial value of loop counter is set outside the loop limits 12 When open fails, don't prompt for a file name. Instead, set the err variable to a positive number. 0 = normal. 18.4 3. trace and untrace. These commands take a single variable as an argument. The trace command is actually a command to the compiler. Whenever an instruction is compiled that could possibly change the value of the given variable, a putc type instruction is also compiled into the code, which will display the value of the variable at run time. The untrace command will turn trace off for the given variable. The purpose of the trace command is to allow you to see where a particular variable is being modified, and to track its value during the execution of a program. Example: Program ═════════════════════ int i trace i loop for i = 1 to 5 i = i + 1 repeat untrace i i = 10 run Execution ═════════════════════════════════════ ** S=8, P=18, L=233, M=410 ** Trace activated at line number 6 Integer counter: i = 1 Final value = 5 Trace activated at line number 8 Integer variable: i = 2 Trace activated at line number 6 Integer counter: i = 3 Final value = 5 Trace activated at line number 8 Integer variable: i = 4 Trace activated at line number 6 Integer counter: i = 5 Final value = 5 Trace activated at line number 8 Integer variable: i = 6 Ready for program 18.5 4. examine. When all else fails, there is always the examine command. examine puts your program into step mode. From the point where the examine occurs, you can step through your program by pressing the F9 key. While in examine mode, you can examine the value of any string, bit-string, integer or real variable; and you can change the value of any such variable. You can also exit examine mode and return to normal operation of the program. Examine statements can be put anywhere in your program, and you can put in as many as you like. examine is a tedious way to find program bugs, but for some types of problems, it has proven to be the fastest way to find the source of trouble. When you enter examine mode, you are presented with the following menu: *** Entering examine mode at source line number <#> *** /x,<var> = examine value of variable /h,<var> = examine value of variable in hex format /c,<var> = change value of variable F9 = step one line in program /e = exit examine mode; continue with program <var> = [<procedure name>]<variable name>(<subscripts>) (a dot may be substituted for [<current procedure>]) It is important understand how to specify a variable. If you want to know the value of the integer variable i, for example, you need to specify whether this is a global variable (a variable in the main program), or a variable in a procudure. If you precede the variable with a dot, it means that you are specifying a local variable (within the current procudure). Example: Program ═════════════════════ int i loop for i = 1 to 1 perform pro1 repeat stop procedure pro1 int i i = 20 examine perform pro2 return procedure pro2 int i i = 10000 return run Execution ═════════════════════════════════════ ** S=16, P=29, L=237, M=410 ** *** Entering examine mode at source line number 11 *** /x,<var> = examine value of variable /h,<var> = examine value of variable in hex format /c,<var> = change value of variable F9 = step one line in program /e = exit examine mode; continue with program <var> = [<procedure name>]<variable name>(<subscripts>) (a dot may be substituted for [<current procedure>]) /x,i Integer variable i = 1 /x,.i Integer variable .i = 20 /x,[pro2]i Integer variable [pro2]i = 0 /e Ready for program Examine mode will work only when your source program comes from a file, not from a window. This is because the interpreter needs to have runtime access to the source code in order to display the lines of your program at run time. When in examine mode, you will see 15 lines of your program at a time, with a highlight on the fourth line (i.e., you can see three lines back of where you currently are and eleven lines ahead of where you currently are). If you change to another window while in examine mode, when you change back, the program lines are no longer there. Simply press the Enter key, and they will return. ────────────────────────────────────────────────────────────────── ┌─────────────────────────────┐ │ 19. Conditional compiles │ │ and other features of the │ │ Compiler │ └─────────────────────────────┘ 19.1 Zbex provides you with some compile time features that can add flexibility and readability to your programs. The following commands must start in column one. 19.2 #define The #define command has two fields, separated by one more blanks. The second field is defined as a compile time replacement for the first field. The effect during compilation is that whenever the compiler finds a string of characters (outside a put statement) that matches the first field, it substitutes the string of characters in the second field. This feature allows you to identify arbitrary constants (sometimes called "magic numbers") with a name, so that these can later be easily found and changed. It also allows you to identify program parameters by name (e.g., the size of arrays) so that these can be easily changed. The #define command is also used to set the value of boolean variables used in conditional compiles. The variable is identified in the first field, and its value is set by the second field. The value is 1, if and only if the second field is a "1". Example: Program ═════════════════════ #define LIMIT 40 int h,i,j(LIMIT) j(1) = 1 j(2) = 1 loop for i = 3 to LIMIT j(i) = j(i-1) + j(i-2) repeat loop for i = 1 to LIMIT putc ~j(i) ... h = i / 5 if rem = 0 putc end repeat putc run Execution ═════════════════════════════════════ ** S=15, P=57, L=243, M=450 ** 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 14930352 24157817 39088169 63245986 102334155 Ready for program 19.3 #if, #else, #endif. This group of commands works much like the if/else/end group in Zbex, except that the effect is to tell the compiler which lines of code to compile. The #if command requires a boolean variable, whose value must be set by a #define command (see above). If the value of the variable is 1, the lines following the #if will be compiled, up to the #else (which is optional) or the #endif of the group. It is possible to have nested #if-#else-#endif groups. Example: Program ═════════════════════ #define OPTION_1 1 #define OPTION_2 0 str name.30,subname.10(3) int i name = "Ronald Wilson Reagan" mpt = 0 loop for i = 1 to 3 subname(i) = txt(name,[' ']) repeat putc The candidate's name is ... #if OPTION_1 putc ~subname(1) ... #if OPTION_2 putc ~subname(2) ... #else putc ~subname(2){1} . ... #endif #endif putc ~subname(3) run Execution ═════════════════════════════════════ ** S=12, P=56, L=263, M=431 ** The candidate's name is Ronald W. Reagan Ready for program 19.4 #process. This command allows you to identify groups of lines that you want included in a compile. Groups of lines are identified by letters, caps and small (52 groups total). The compiler will include all groups identied by letters found on #process command line. You may use the #process command more than once. Program lines belonging to a group are identified by an ampersand "&" in column one, followed by a single letter in column two and a blank space in column three. Example: Program ═════════════════════ #process A b D &A putc Option A &b putc Option B &c putc Option C &D putc Option D &E putc Option E &F putc Option F run Execution ═════════════════════════════════════ ** S=4, P=17, L=231, M=410 ** Option A Option B Option D Ready for program The #process command is very useful for removing debug statements from the compile process after your program is running properly. I have found it helpful to keep debug statements available in case something goes wrong with one of my programs later on. The advantage is that I don't have to rethink the debugging process from scratch. By using the %<letter> method for removing these lines, I can turn them back on in an instant by simply including the proper process statement at the top my program. In the small example below, I can turn on the dputc statement by replacing the "*" in front of process with a "#". Program ═════════════════════ *process A int i loop for i = 1 to 10 &A dputc ~i repeat run 19.5 #autodef. If you are writing a short program and you are feeling too lazy to declare your variables, you may be able to use the #autodef command. #autodef automatically declares the letters 'a'..'t' to be integers and the letters 'u'..'z' to be real numbers. All other symbols will be declared as strings with maximum length of 160. Example: Program ═════════════════════ #autodef A = "" loop for i = 1 to 1000 A = A // "x" repeat run Execution ═════════════════════════════════════ ** S=5, P=16, L=237, M=451 ** Run-time error: maximum length of string variable: A has been exceeded at line = 4 attempted length = 161, maximum length = 160 Ready for program 19.6 The Zbex compiler recognizes a few abbreviations that have become standard in other programing languages. These are listed below, along with what they represent. Abbreviation Expanded to ... ────────────── ───────────────────────── ++i i = i + 1 (integer only) --i i = i - 1 (integer only) x += 1.0 x = x + 1.0 (integer and real) i -= 10 i = i - 10 " " " y *= x + 3.0 y = y * (x + 3.0) " " " k /= 4 k = k / 4 " " " i &= 0x01 i = i & 0x01 (integer only) i |= 0x04 i = i | 0x04 " " i >>= 3 i = i >> 3 " " i <<= 4 + j i = i << (4 + j) " " ────────────────────────────────────────────────────────────────── ┌───────────────────────────────────────────────────────────────┐ │ 20. Controlling the (text) display with putc (Advanced Topic) │ └───────────────────────────────────────────────────────────────┘ 20.1 Zbex provides you with some escape sequences that allow you to control certain parameters of the display from your Zbex program. Use putc to send these sequences to Dmuse. 1. Two characters escape sequences Sequence Effect ──────── ────────────── <esc> A Cursor Up <esc> B Cursor Down <esc> C Cursor Right <esc> D Cursor Left <esc> E set Tab <esc> F Cursor End of Window (End) <esc> G (none) <esc> H Cursor Top of Window (Home) <esc> I Tab <esc> J Clear Display <esc> K Clear Line <esc> L Insert Line <esc> M Delete Line <esc> N Copy Line to Buffer <esc> O Insert Line from Buffer <esc> P Delete Character <esc> Q Enter Insert Mode (if ins_mode = OFF) <esc> R Leave Insert Mode (if ins_mode = ON) <esc> S Scroll Up <esc> T Scroll Down <esc> U Page Up <esc> V Page Down <esc> W Scroll Right <esc> X Scroll Left <esc> Y Cursor to Column 0 <esc> Z Cursor to End of line 2. Multiple character sequences A. Cursor Addressing 1. Relative addressing: move cursor to <x,y> position in the currently displayed screen. The <x> position in this case is actually absolute, i.e., same as <c> below. Numbering starts with 0, not 1. Numbers connected to "Y" must have at least two digits. (e.g., 09 = 9). <esc>&a <column number> x <row number> Y <esc>&a <column number> X <esc>&a <row number> Y 2. Absolute addressing: move cursor to <c,r> position in the currently displayed window. Scrolling works, but you cannot move to a row below the data at the bottom a window. Numbering starts with 0. Numbers connected to "R" must have at least two digits. (e.g., 09 = 9). <esc>&a <column number> c <row number> R <esc>&a <column number> C <esc>&a <row number> R 3. Cursor Relative Addressing: move cursor a relative distance from its present location <+or-c,+or-r> on the screen. <esc>&a <+or-><column number> c <+or-><row number> R <esc>&a <+or-><row number> r <+or-><column number> C <esc>&a <+or-><column number> C <esc>&a <+or-><row number> R Note: Actually, it is possible to mix and match any of these commands. The end of the sequence is signified by a capital command letter. This means that if we want to back up the cursor 20 positions and goto row 0, we would send the command <esc>&a-20c00R B. Display Enhancements <esc>&d@ normal wind_txt <esc>&dA enhancement 1 <esc>&dB enhancement 2 <esc>&dC enhancement 3 <esc>&dD enhancement 4 <esc>&dE enhancement 5 <esc>&dF enhancement 6 <esc>&dG enhancement 7 <esc>&dH enhancement 8 <esc>&dI enhancement 9 <esc>&dJ enhancement 10 <esc>&dK enhancement 11 <esc>&dL enhancement 12 <esc>&dM enhancement 13 <esc>&dN enhancement 14 <esc>&dO enhancement 15 ────────────────────────────────────────────────────────────────── ┌─────────────────────────────────┐ │ 21. Graphics (Advanced Topic) │ └─────────────────────────────────┘ 21.1 The normal mode of operation for an Zbex mode is text mode, using the current Dmuse window for input and output. It is possible with Zbex to display data in graphics mode (music, for example). Zbex is not designed for constructing images in graphics mode. It is not good at drawing lines or circles. It is good at displaying letters and other glyph like characters, and it is reasonably efficient at displaying bitmap images that have been constructed "off screen". 21.2 There are eleven▂Zbex instructions that relate to graphics. bitmode = change to graphics display textmode = change to normal, character based, window display setup = setup a string as graphics buffer activate = identify a graphics buffer with a particular graphics plane setb = turn on a group of bits in a graphics buffer clearb = turn off a group of bits in a graphics buffer getk = get a keystroke (spin until keystroke arrives) getx = if a keystroke is pending, deliver it; otherwise return 0 dscale2 = scale a graphics buffer to 50% of its original size dscale3 = scale a graphics buffer to 33% of its original size dscale5 = scale a graphics buffer to 66% of its original size 21.3 In order to use these instructions, you need to know how the graphics mode is implemented in Zbex and how this relates to the Linux X Diplay window. Dmuse implements graphics as a set of four single-plane Pixmaps, each one designed to display its contents in a different color. As a concept, think of these planes as a stacked set, labled 1, 2, 3, and 4, with 4 on the top. Imagine that the background of the bottom plane is always white, and the foreground is the base color, black. Then think of the remaining three planes as being like "tranperancies," with one opaque color for writing and a background that is "see-through." The default colors for planes 2 thorough 4 are respectively, green, red, and blue. The planes never change their position, but things written to them can be erased and re-written in a different places. If the "blue" (top) image is fixed and something on the "red" (3rd plane) is moved, this can give the impression that the red image is changing under the blue image. Parts of the red image that were previously "eclipsed" by the blue image would appear to move out from behind the blue image. Similarly, if the "red" plane is fixed and something on the "blue" plane is moved (like a blue cursor shape, for example), the blue cursor would appear to move around on top of the red plane, but never destroying what was written there (cursor like behavior). 21.4 These Pixmap planes form the "hand-off" between what Zbex wants to display and what Dmuse passes on to X Windows. Dmuse knows how to combine the information on the Pixmaps so that the rules of eclipse are followed. All the Zbex programmer needs to concern himself/herself about is what is written to each of the four Pixmap color planes. Zbex does this through a mechanism called a graphics buffer. Zbex does not have a separate data type called a graphics buffer; it uses strings for this. The setup instruction is provided to make it easy to format a string as a graphics buffer. A graphics buffer consists of a base plane and zero to nine "scratch" planes. When Zbex writes a graphics buffer to one of the four Pixmap planes, it does this by first clearing the base plane, then copying (OR-ing) all of the set (on = 1) bits on each scratch plane to the base plane. The base plane is then copied to the specified Pixmap plane. This system has the advantage that features such as musical staff lines on one scratch plane are uneffected by movements of notes and other musical notation on another scratch plane. 21.5 The format of a graphics buffer (string) is this: bytes 1-5: array paramteers bytes 1-2: m = number of BYTES in the horizontal dimension bytes 3-4: n = number of rows in the vertical dimension byte 5: p = number of "scratch" planes plus 1 bytes 6-10: activation parameters (dynamic at run time) byte 6: Pixmap plane to which buffer was last written 0 = no plane designated 1 to 4 = designated Pixmap plane; buffer is said to be active bytes 7-8: x = x offset of last activation command bytes 9-10: y = y offset of last activation command bytes 11-18: left, top, right, and bottom exposure boundaries bytes 11-12: = left boundary (x1) bytes 13-14: = top boundary (y1) bytes 15-16: = right boundary (x2) bytes 17-18: = bottom boundary (y2) bytes 19-20: unused bytes 21... m*n*p bytes of data space If m*n*p + 20 > declared length of string, a length error occurs If m*n*p = 0, the string is invalid as a graphics buffer 21.6 The setup instruction is designed to simplify the process of formating a string as a graphics buffer. The setup instruction takes a string name, and three integer variables. It is functionally equivalent to the following Zbex code: setup s,i,j,k s = zpd(i*j*k+20) /* check length */ s{1,2} = ch2(i) s{3,2} = ch2(j) s{5} = ch(k) s{11,2} = ch2(0x3fff) s{13,2} = ch2(0x3fff) s{15,2} = ch2(0) s{15,7} = ch2(0) The exposure boundaries are initially set to opposite extremes. When the first write to buffer s occurs, these limits take on real-time values. A graphics buffer need not have the same dimensions as a Pixmap; it may be larger or smaller in either dimension. Only that portion of the buffer lying within the Pixmap's dimensions will be written to the Pixmap plane. 21.7 The activate instruction provides a means of associating a particular graphics buffer with a particular Pixmap plane, and for actually writing that buffer to that plane. The format for the activate instruction is shown below. activate S-str,int1,int2,int3 S-str = name of a string that serves as a graphics buffer int1 = horizontal offset expressed in BYTES for mapping S-str onto its display plane int2 = vertical offset expressed in ROWS for mapping S-str onto its display plane int3 = action parameter -1 = flush buffer to Pixmap plane if byte 6 of S-str is positive 0 = deactive this buffer; set byte 6 of S-str to 0 1,2,3,4 = write buffer to Pixmap plane <#> set byte 6 of S-str to <#> 5 = erase area covered by S-str on Pixmap plane designated by byte 6 of S-str 11,12,13,14 = set byte 6 of S-str to <#-10> but do not write buffer to Pixmap plane The first two integer expressions supplied by the instruction indicate the values of the <x,y> offsets for mapping S-str to the display. The <x> coordinate is expressed in BYTES, not bits. <0,0> is the upper left-hand corner; and positive offsets move down and to the right. The third integer expression, p, determines the action of the activate instruction. If p = 0, action is minimal. Byte 6 of S-str is simply set to 0, thus "deactivating" the variable. If p = 5, this means that the Pixmap plane designated by byte 6 (assuming its value is 1--4) is erased (set to 0) over the area covered by S-str (as offset by the x and y coordinates in bytes 7--10). If p = 1, 2, 3, or 4, then a write to Pixmap plane <p> will occur. If p = 11, 12, 13, or 14, S-str will be activated on Pixmap plane (p-10), but a write to that plane will not occur. Once the these actions are completed, and if p = 1, 2, 3, 4, or 5, Dmuse combines all color Pixmaps in the appropriate way and sends them to the Display screen. If the offset <x,y> expressed in the first two integer expressions of the instruction is different than the the previous offsets stored in bytes 7--10 of S-str, AND the value of byte 6 and p are the same, then an erase of the former area will occur before the write. In the case where S-str covers an area smaller than the graphics bitplane, this will have the effect of "moving the image" from one location to another; in the case where S-str covers an area larger than the graphics bitplane, this will be an effective way to implement "panning" or "scrolling" of a larger image. If p = -1, this asks that the portion of S-str described by the rectangle <x1,y1>, <x2,y2> be written to the Pixmap plane referenced by byte 6 of S-str. Of course, if byte-6 = 0, no write will occur. This use of the activate instruction is new with the Linux version of Dmuse. In the older DOS version, it was quick and easy to write to the computer display screen, and every call to setb/clearb (described below) did this automatically. In X Windows every write to a color Pixmap requires a write from the X Client to the X Server, so we want to keep the number of these writes to minimum. The graphics buffers are part of the Client application, so calls to setb and clearb are fast and require no "X" communication. But we need a way to "flush" these buffers to their destination Pixmaps (byte 6) at selected points in Zbex programs. This explains the purpose of the exposure boundaries, bytes 11-18 in S-str. They outline the rectangle containing all setb/clearb writes since the last flush command. After the flush write is completed the exposure boundaries are reset to <0x3fff,0x3fff>, <0,0>. Note that the flush version of activate (activate S-str,0,0,-1) DOES NO WRITING to the Display screen; this can only be done with activate in its regular form. Also note that the x and y values are ignored in this call. The idea is that this information is already stored in bytes 1-4 of S-str. Flush simply does all at once what use to be done piecemeal by setb and clearb. 21.8 The real work of building a graphics page is in turning bits on and off bits in the graphics buffer. Theoretically, this could be done using conventional Zbex instructions, but this would be time consuming to program and the code would be slow to execute. Instead, Zbex has two specially designed instructions, setb and clearb, to do this in the fastest possible manner. These instructions come in two formats: one using a bit-string array as a source, and the other using an integer array as a source. The operation of the instruction under each of these formats is slightly different. The only difference between setb and clearb is that setb turns bits on and clearb turns bits off. Format 1 ──────── setb/clearb S-str,A-bstr,int1,int2,int3,int4,int5,int6; S-str = name of a string that serves as a graphics buffer A-bstr = a array of bit strings containing a collection of bitmap images int1 = horizontal offset expressed in BITS for the destination of a bitmap image in S-str int2 = vertical offset expressed in ROWS for the destination of bitmap image in S-str int3 = height of bitmap image to be transferred int4 = width of bitmap image to be transferred int5 = starting row in A-bstr for the bitmap image (number of rows to transfer is <int3> int6 = destination plane in S-str (1 = base plane, 2 = scratch plane 1, etc.) Format 2 ──────── setb/clearb S-str,A-int,int1,int2,int3,int4: S-str = name of a string that serves as a graphics buffer A-int = an array of integers (treated as 32 bit wide bit strings) representing a collection of bitmap images. int1 = horizontal offset expressed in BITS for the destination of a bitmap image in S-str int2 = vertical offset expressed in ROWS for the destination of bitmap image in S-str int3 = array element (number) in A-int. This element contains the offset (element number) in A-int for the bitmap data to be transferred. At the offset address, the first integer (array element) contains four 8-bit data fields: the height and width of the image, and the local horizonal and vertical offsets of image relative to the <x,y> placement parameters. The second integer contains one (left justified) 8-bit field: the increment to the <x> placement parameter after a set/clear operation. First integer contains: ┌──────┬──────┬───────────────┬───────────────┐ │height│width │local h. offset│local v. offset│ └──────┴──────┴───────────────┴───────────────┘ Second integer contains: ┌────────────────┬────────────────────────────┐ │increment to <x>│ | zeros | │ └────────────────┴────────────────────────────┘ Third and following integers contain the bitmap image. If the image is less than 33 bits wide, each row of the image will require one integer; if the image is less than 65 bits wide, each row of the image will require two integers; etc. int4 = destination plane in S-str If byte 6 of S-str is 1,2,3 or 4 (S-str is activated), then setb (clearb) will adjust the size of the exposure rectangle (bytes 11-18 in S-str) to include the area changed by the write. This way, with one instruction you can look up a bitmap image, transfer it to a graphics buffer, record this action for later "flushing," and increment the variable which specifies the <x> position for writing. Operation ───────── The first variable is interpreted as a graphics buffer. It acts as <p> independent bitmap planes, where <p> is the dimension specified in byte 5 of S-str (see diagram). The second variable is a one dimensional bitstring array, A-bstr, or a one dimensional integer array, A-int(q). This variable S-str(m,n,p) functions as a source for a set of p┌──────────────┐ bitmats (see 2nd diagram). ┌┴─────────────┐│ .┌┴─────────────┐││ Now we can describe the action of the .┌┴─────────────┐│││ command. Let's talk about setb. This .┌┴─────────────┐││││ instruction is used to turn on bits 3┌┴─────────────┐│││││ in S-str. The integers supplied with 2┌┴─────────────┐││││││ the instruction determine what pattern 1┌┴─────────────┐│││││├┘ of bits is turned on and where it is │ ↑ │││││├┘ turned on. The first two integers are │◄┼─ m bytes ─►││││├┘ the <x,y> offset in S-str where the │ │ │││├┘ pattern will start. Notice that <x> is │n rows ││├┘ given as a BIT offset, not a byte offset. │ │ │├┘ <0,0> is the top lefthand corner of S-str. │ ↓ ├┘ The meaning of the remaining variables depends └──────────────┘ on the format. In the case of the first format type (using a bit-string source, the third integer is the height (vertical dimension) A-bstr(.) of the "box" image to be copied to S-str. The ┌───────┐ fourth integer is the width (horizontal dimension) │ │ measured in BITs. This is the maximum width of this │ │ particular image in A-bstr. The fifth integer is ├───────┤ the array row number in A-bstr(.) of the first bit ├───────┤ string to copy. Notice that since this number is a ├───────┤ subscript, it follows the rules of subscripts, │ │ e.g., it cannot be less than one. The sixth integer │ │ is a value from 1 to <p> and is the plane to which ├───────┤ the copy should be made. │ │ └───────┘ This technique will allow us to store entire fonts in long, bitstring arrays. To select a glyph for copying, we simply need to know the beginning array row number in A-bstr(.) and the number of rows (height). The fact that we have <p> planes to write in means that we can build up complex notations such as music from a series of glyphs. It also means that we can selectively turn on and off various glyphs, without disturbing other glyphs. For example, we could turn off a music note and turn it back on in another location, without disturbing the music staff lines in the background. The operation of the second format (using an A-int source) is slightly more complicated, but for certain kinds of tasks, it is more streamline and faster. The idea is that if we are using these instructions to turn on and off letters of a font, much of the information about the letters is known and can therefore be put directly in the integer array. In this case, the first and second integers (represent the <x,y> position) must be integer variables, not literal numbers or expressions, A-int(.) as allowed in the first format. It is possible 1┌───────┐ for this instruction to modify first variable ├ ┤ <x> after the image is copied to the graphics ├ ┤ buffer. The third integer (expression) is an ├ ┤ offset to an array element in A-int. The fourth 97├───────┤ integer is a value from 1 to <p> and is the ┌<│pointer│ plane in the graphics buffer to which the copy │ ├───────┤ should be made. │ │ │ │ ├───────┤ One way of conceptualizing the role of the third └►│param1 │ integer (expression) is to think of it as acting │───────│ like a font number. For example, the number 97 │param2 │ might point to a small 'a'. This would mean that │───────│ the 97-th entry in A-int would contain the offset │bit │ to the data for the 'a'. In effect, this offset │image │ would be the equivalent of the fifth integer in │ │ the first format. The first two integers of the ├───────┤ data block for the letter 'a' are reserved for │ │ data about the letter. The first integer contains the following information: high order byte: height of the box (equivalent of third integer) byte 2: width of the box (equivalent of fourth integer) byte 1: horz offset (signed) added to <x> low order byte: vert offset (signed) added to <y> The high order byte of second integer contains the increment to <x> following execution of instruction. The third and following integers contain the data for the letter 'a'. Note that the number of integers required per row will depend on the width of the box (given in byte 2). If the width is 32 or less, one integer per row is required; 64 or less, two integers per row are required; etc. One can see that the integer array, A-int, must be constructed rather carefully by the user programmer. The payoff, however, is much greater speed at run time for applications that write fonts to the screen in a linear fashion. setb is used to turn bits on; clearb is used to turn bits off. The exact mechanism for writing will depend on whether bits are being set or cleared. In the first case, the specified plane needs to be OR-ed to plane 1 (the base plane in the graphics buffer); in the second case, all of the planes 2 and higher need to be OR-ed together and the result placed in plane 1 (the base plane). 21.9 When a Zbex program is compiled, a flag is set if graphics instructions are used anywhere in the program. When the program is handed off to the Zbex interpreter, if this flag is set, the interpreter allocates a set of four backup color Pixmaps for storing the program's graphics. The four operational color Pixmaps are also cleared at this time, but no writing is done to the user's X-Window display. If the Zbex program's operation is suspended (either by a switch to text mode, or by a change to another window), the four operational color Pixmaps are automatically copied to the backup Pixmaps for this program. If a Zbex program is re-entered (by a window change), the backup color Pixmaps are automatically copied back to the operational color Pixmaps, and if the program is currently running in graphics mode, the color Pixmaps are then written to the user's X-Window display. In this way, it is possible to have more than one Zbex program running in graphics mode. 21.10 In order to run in graphics mode, Zbex needs to have a mechanism for clearing the screen and for determining the size of the active screen. This is the task of the bitmode instruction. The bitmode instruction has the following format: bitmode int-exp,int,int The format consists of the instruction, one integer expression and two integer variables. The value of the integer expression must be 1 or 2. This value determines the operation of the instruction. (1) Since Zbex programs should be able to run in any size X-window there needs to be a way for a Zbex program to access the current size of the Display Window in which it is running. (2) Zbex also needs to have a way of clearing all four of the color Pixmaps and asking that the Display be cleared as well. If the value of int-exp is 1, then only task (1) will be done. If the value of int-exp is 2, both task (1) and task (2) will be done. The values returned in the two integers are the current width and height of the user's X-Window Display. The bitmode instruction may be issued anywhere in a Zbex program. If the display is not already using bitmap graphics, the window display mechanism will be switched from text (glyph) based to bitmap based. 21.11 The Zbex textmode instruction is used to switch the display from graphics mode to character mode. The current window with its current contents will reappear. The contents of the four operational color Pixmaps will be copied to the backup Pixmaps. 21.12 The normal way to send Zbex imput from the keyboard is with the getc command. This is fine for sending the program lines from a window, but it does not give the programmer control of the keyboard. In graphics mode, we often need to have control of the keyboard so that we can program the effect of certain keystrokes. For example, we may want to scroll the graphics screen using the cursor keys. The way to get keystroke input directly from the keyboard is with the getk instruction. getk takes one integer variable as an input argument. Listed below are the keystrokes from the keyboard which will return a value to getk (not all keystrokes will). The values are represented in HEX format. ┌───────────────────────────────┬─────────────────────────────────┐ │ SORTED BY NUMBER │ SORTED BY KEYSTROKE │ │ ════════════════════ │ ═══════════════════════ │ │Value │ Value │ │Returned Keystroke │ Keystroke Returned│ │─────── ────────────────── │ ──────────────────── ─────────│ │0x10001 <ctrl-shft> a │ <esc> 0x1001b│ │0x10002 <ctrl-shft> b │ F1 0x31000│ │0x10003 <ctrl-shft> c │ <shft> F1 0x31010│ │0x10004 <ctrl-shft> d │ <ctrl> F1 0x31020│ │0x10005 <ctrl-shft> e │ <ctrl-shft> F1 0x31040│ │0x10006 <ctrl-shft> f │ F2 0x31001│ │0x10007 <shft-alt> 9 │ <shft> F2 0x31011│ │0x10009 <shft-alt> 0 │ <ctrl> F2 0x31021│ │0x10008 <ctrl-shft> g │ <ctrl-shft> F2 0x31041│ │0x1000a <shft-alt> - │ F3 0x31002│ │0x1000b <ctrl-shft> k │ <shft> F3 0x31012│ │0x1000c <ctrl-shft> h │ <ctrl> F3 0x31022│ │0x1000d <ctrl-shft> l │ <ctrl-shft> F3 0x31042│ │0x1000e <ctrl-shft> m │ F4 0x31003│ │0x1000f <ctrl-shft> o │ <shft> F4 0x31013│ │0x10010 <ctrl-shft> p │ <ctrl> F4 0x31023│ │0x10011 <ctrl-shft> q │ <ctrl-shft> F4 0x31043│ │0x10012 <ctrl-shft> r │ F5 0x31004│ │0x10013 <ctrl-shft> s │ <shft> F5 0x31014│ │0x10014 <ctrl-shft> t │ <ctrl> F5 0x31024│ │0x10015 <ctrl-shft> u │ <ctrl-shft> F5 0x31044│ │0x10016 <ctrl-shft> v │ F6 0x31005│ │0x10017 <ctrl-shft> w │ <shft> F6 0x31015│ │0x10018 <ctrl-shft> x │ <ctrl> F6 0x31025│ │0x10019 <ctrl-shft> y │ <ctrl-shft> F6 0x31045│ │0x1001a <shft-alt> = │ F7 0x31006│ │0x1001b <esc> │ <shft> F7 0x31016│ │0x1001c <ctrl-shft> n │ <ctrl> F7 0x31026│ │0x1001e <ctrl-shft> i │ <ctrl-shft> F7 0x31046│ │0x1001f <ctrl-shft> j │ F8 0x31007│ │0x10020 SPACE │ <shft> F8 0x31017│ │0x10021 <shft> 1 │ <ctrl> F8 0x31027│ │0x10022 <shft> ' │ <ctrl-shft> F8 0x31047│ │0x10023 <shft> 3 │ F9 0x31008│ │0x10024 <shft> 4 │ <shft> F9 0x31018│ │0x10025 <shft> 5 │ <ctrl> F9 0x31028│ │0x10026 <shft> 7 │ <ctrl-shft> F9 0x31048│ │0x10027 ' │ F10 0x31009│ │0x10028 <shft> 9 │ <shft> F10 0x31019│ │0x10029 <shft> 0 │ <ctrl> F10 0x31029│ │0x1002a <shft> 8 │ <ctrl-shft> F10 0x31049│ │0x1002b <shft> = │ F11 0x3100a│ │0x1002c , │ <shft> F11 0x3101a│ │0x1002d - │ <ctrl> F11 0x3102a│ │0x1002e . │ <ctrl-shft> F11 0x3104a│ │0x1002f / │ F12 0x3100b│ │0x10030 0 │ <shft> F12 0x3101b│ │0x10031 1 │ <ctrl> F12 0x3102b│ │0x10032 2 │ <ctrl-shft> F12 0x3104b│ │0x10033 3 │ ` 0x10060│ │0x10034 4 │ <shft> ` 0x1007e│ │0x10035 5 │ 1 0x10031│ │0x10036 6 │ <shft> 1 0x10021│ │0x10037 7 │ <shft-alt> 1 0x100ad│ │0x10038 8 │ 2 0x10032│ │0x10039 9 │ <shft> 2 0x10040│ │0x1003a <shft> ; │ <shft-alt> 2 0x1009d│ │0x1003b ; │ 3 0x10033│ │0x1003c <shft> , │ <shft> 3 0x10023│ │0x1003d = │ <shft-alt> 3 0x1009c│ │0x1003e <shft> . │ 4 0x10034│ │0x1003f <shft> / │ <shft> 4 0x10024│ │0x10040 <shft> 2 │ <shft-alt> 4 0x1009b│ │0x10041 <shft> a │ 5 0x10035│ │0x10042 <shft> b │ <shft> 5 0x10025│ │0x10043 <shft> c │ <shft-alt> 5 0x1009f│ │0x10044 <shft> d │ 6 0x10036│ │0x10045 <shft> e │ <shft> 6 0x1005e│ │0x10046 <shft> f │ <shft-alt> 6 0x100fd│ │0x10047 <shft> g │ 7 0x10037│ │0x10048 <shft> h │ <shft> 7 0x10026│ │0x10049 <shft> i │ <shft-alt> 7 0x100ac│ │0x1004a <shft> j │ 8 0x10038│ │0x1004b <shft> k │ <shft> 8 0x1002a│ │0x1004c <shft> l │ <shft-alt> 8 0x100ab│ │0x1004d <shft> m │ 9 0x10039│ │0x1004e <shft> n │ <shft> 9 0x10028│ │0x1004f <shft> o │ <shft-alt> 9 0x10007│ │0x10050 <shft> p │ 0 0x10030│ │0x10051 <shft> q │ <shft> 0 0x10029│ │0x10052 <shft> r │ <shft-alt> 0 0x10009│ │0x10053 <shft> s │ - 0x1002d│ │0x10054 <shft> t │ <shft> - 0x1005f│ │0x10055 <shft> u │ <alt> - 0x100aa│ │0x10056 <shft> v │ <shft-alt> - 0x1000a│ │0x10057 <shft> w │ = 0x1003d│ │0x10058 <shft> x │ <shft> = 0x1002b│ │0x10059 <shft> y │ <alt> = 0x100a9│ │0x1005a <shft> z │ <shft-alt> = 0x1001a│ │0x1005b [ │ BackSpace 0x3040a│ │0x1005c \ │ <shft> BackSpace 0x3040b│ │0x1005d ] │ Tab 0x30810│ │0x1005e <shft> 6 │ <left-shft> Tab 0x30811│ │0x1005f <shft> - │ <right-shft> Tab 0x30812│ │0x10060 ` │ <ctrl> Tab 0x30814│ │0x10061 a │ <left-ctrl-shft> Tab 0x30815│ │0x10062 b │ <right-ctrl-shft> Tab 0x30813│ │0x10063 c │ q 0x10071│ │0x10064 d │ <shft> q 0x10051│ │0x10065 e │ <ctrl> q 0x30471│ │0x10066 f │ <alt> q 0x100da│ │0x10067 g │ <ctrl-shft> q 0x10011│ │0x10068 h │ <shft-alt> q 0x100d6│ │0x10069 i │ w 0x10077│ │0x1006a j │ <shft> w 0x10057│ │0x1006b k │ <ctrl> w 0x30477│ │0x1006c l │ <alt> w 0x100c2│ │0x1006d m │ <ctrl-shft> w 0x10017│ │0x1006e n │ <shft-alt> w 0x100d2│ │0x1006f o │ e 0x10065│ │0x10070 p │ <shft> e 0x10045│ │0x10071 q │ <ctrl> e 0x30465│ │0x10072 r │ <alt> e 0x100bf│ │0x10073 s │ <ctrl-shft> e 0x10005│ │0x10074 t │ <shft-alt> e 0x100b7│ │0x10075 u │ r 0x10072│ │0x10076 v │ <shft> r 0x10052│ │0x10077 w │ <ctrl> r 0x30472│ │0x10078 x │ <alt> r 0x100c4│ │0x10079 y │ <ctrl-shft> r 0x10012│ │0x1007a z │ t 0x10074│ │0x1007b <shft> [ │ <shft> t 0x10054│ │0x1007c <shft> \ │ <ctrl> t 0x30474│ │0x1007d <shft> ] │ <alt> t 0x100c9│ │0x1007e <shft> ` │ <ctrl-shft> t 0x10014│ │0x1007f <ctrl-shft> z │ <shft-alt> t 0x100d5│ │0x10091 <shft-alt> i │ y 0x10079│ │0x10092 <shft-alt> o │ <shft> y 0x10059│ │0x1009b <shft-alt> 4 │ <ctrl> y 0x30479│ │0x1009c <shft-alt> 3 │ <alt> y 0x100cb│ │0x1009d <shft-alt> 2 │ <ctrl-shft> y 0x10019│ │0x1009e <shft-alt> p │ <shft-alt> y 0x100d1│ │0x1009f <shft-alt> 5 │ u 0x10075│ │0x100a8 <shft-alt> / │ <shft> u 0x10055│ │0x100a9 <alt> = │ <ctrl> u 0x30475│ │0x100aa <alt> - │ <alt> u 0x100bb│ │0x100ab <shft-alt> 8 │ <ctrl-shft> u 0x10015│ │0x100ac <shft-alt> 7 │ <shft-alt> u 0x100b8│ │0x100ad <shft-alt> 1 │ i 0x10069│ │0x100ae <shft-alt> , │ <shft> i 0x10049│ │0x100af <shft-alt> . │ <ctrl> i 0x30469│ │0x100b0 <alt> o │ <alt> i 0x100cd│ │0x100b1 <alt> p │ <ctrl-shft> i 0x1001e│ │0x100b2 <alt> [ │ <shft-alt> i 0x10091│ │0x100b3 <alt> f │ o 0x1006f│ │0x100b4 <alt> d │ <shft> o 0x1004f│ │0x100b5 <shft-alt> j │ <ctrl> o 0x3046f│ │0x100b6 <shft-alt> d │ <alt> o 0x100b0│ │0x100b7 <shft-alt> e │ <ctrl-shft> o 0x1000f│ │0x100b8 <shft-alt> u │ <shft-alt> o 0x10092│ │0x100b9 <alt> j │ p 0x10070│ │0x100ba <alt> k │ <shft> p 0x10050│ │0x100bb <alt> u │ <ctrl> p 0x30470│ │0x100bc <alt> m │ <alt> p 0x100b1│ │0x100bd <shft-alt> c │ <ctrl-shft> p 0x10010│ │0x100be <shft-alt> m │ <shft-alt> p 0x1009e│ │0x100bf <alt> e │ [ 0x1005b│ │0x100c0 <alt> z │ <shft> [ 0x1007b│ │0x100c1 <alt> x │ <alt> [ 0x100b2│ │0x100c2 <alt> w │ <shft-alt> [ 0x100fe│ │0x100c3 <alt> a │ ] 0x1005d│ │0x100c4 <alt> r │ <shft> ] 0x1007d│ │0x100c5 <alt> s │ <alt> ] 0x100db│ │0x100c6 <shft-alt> g │ <shft-alt> ] 0x100fa│ │0x100c7 <shft-alt> a │ \ 0x1005c│ │0x100c8 <alt> b │ <shft> \ 0x1007c│ │0x100c9 <alt> t │ a 0x10061│ │0x100ca <alt> n │ <shft> a 0x10041│ │0x100cb <alt> y │ <ctrl> a 0x30461│ │0x100cc <alt> g │ <alt> a 0x100c3│ │0x100cd <alt> i │ <ctrl-shft> a 0x10001│ │0x100ce <alt> h │ <shft-alt> a 0x100c7│ │0x100cf <shft-alt> n │ s 0x10073│ │0x100d0 <shft-alt> x │ <shft> s 0x10053│ │0x100d1 <shft-alt> y │ <ctrl> s 0x30473│ │0x100d2 <shft-alt> w │ <alt> s 0x100c5│ │0x100d3 <shft-alt> z │ <ctrl-shft> s 0x10013│ │0x100d4 <shft-alt> b │ <shft-alt> s 0x100d7│ │0x100d5 <shft-alt> t │ d 0x10064│ │0x100d6 <shft-alt> q │ <shft> d 0x10044│ │0x100d7 <shft-alt> s │ <ctrl> d 0x30464│ │0x100d8 <shft-alt> h │ <alt> d 0x100b4│ │0x100d9 <alt> c │ <ctrl-shft> d 0x10004│ │0x100da <alt> q │ <shft-alt> d 0x100b6│ │0x100db <alt> ] │ f 0x10066│ │0x100dc <alt> ; │ <shft> f 0x10046│ │0x100dd <alt> , │ <ctrl> f 0x30466│ │0x100de <alt> . │ <alt> f 0x100b3│ │0x100df <alt> l │ <ctrl-shft> f 0x10006│ │0x100fa <shft-alt> ] │ g 0x10067│ │0x100fd <shft-alt> 6 │ <shft> g 0x10047│ │0x100fe <shft-alt> [ │ <ctrl> g 0x30467│ │0x30101 ◄- │ <alt> g 0x100cc│ │0x30102 ↑ │ <ctrl-shft> g 0x10008│ │0x30103 -► │ <shft-alt> g 0x100c6│ │0x30104 ↓ │ h 0x10068│ │0x30105 <left-shft> ◄- │ <shft> h 0x10048│ │0x30106 <left-shft> ↑ │ <ctrl> h 0x30468│ │0x30107 <left-shft> -► │ <alt> h 0x100ce│ │0x30108 <left-shft> ↓ │ <ctrl-shft> h 0x1000c│ │0x30109 <ctrl> ◄- │ <shft-alt> h 0x100d8│ │0x30109 <right-shft> ◄- │ j 0x1006a│ │0x3010a <ctrl> ↑ │ <shft> j 0x1004a│ │0x3010a <right-shft> ↑ │ <ctrl> j 0x3046a│ │0x3010b <ctrl> -► │ <alt> j 0x100b9│ │0x3010b <right-shft> -► │ <ctrl-shft> j 0x1001f│ │0x3010c <ctrl> ↓ │ <shft-alt> j 0x100b5│ │0x3010c <right-shft> ↓ │ k 0x1006b│ │0x3010d <alt> ◄- │ <shft> k 0x1004b│ │0x3010e <alt> ↑ │ <ctrl> k 0x3046b│ │0x3010f <alt> -► │ <alt> k 0x100ba│ │0x30110 <alt> ↓ │ <ctrl-shft> k 0x1000b│ │0x30111 <left-shft-alt> ◄- │ l 0x1006c│ │0x30112 <left-shft-alt> -► │ <shft> l 0x1004c│ │0x30113 <right-shft-alt> ◄- │ <ctrl> l 0x3046c│ │0x30114 <right-shft-alt> -► │ <alt> l 0x100df│ │0x30115 <ctrl-shft> ◄- │ <ctrl-shft> l 0x1000d│ │0x30116 <ctrl-shft> ↑ │ ; 0x1003b│ │0x30117 <ctrl-shft> -► │ <shft> ; 0x1003a│ │0x30118 <ctrl-shft> ↓ │ <alt> ; 0x100dc│ │0x30119 <left-shft-alt> ↑ │ ' 0x10027│ │0x3011a <left-shft-alt> ↓ │ <shft> ' 0x10022│ │0x3011b <right-shft-alt> ↑ │ Enter 0x3080c│ │0x3011c <right-shft-alt> ↓ │ <shft> Enter 0x3080d│ │0x30120 PageUP │ <ctrl> Enter 0x3080e│ │0x30121 PageDOWN │ <alt> Enter 0x3080f│ │0x30122 Home │ z 0x1007a│ │0x30123 End │ <shft> z 0x1005a│ │0x30124 <shft> PageUP │ <ctrl> z 0x3047a│ │0x30125 <shft> PageDOWN │ <alt> z 0x100c0│ │0x30126 <shft> Home │ <ctrl-shft> z 0x1007f│ │0x30127 <shft> End │ <shft-alt> z 0x100d3│ │0x30130 <ctrl> PageUP │ x 0x10078│ │0x30131 <ctrl> PageDOWN │ <shft> x 0x10058│ │0x30132 <ctrl> Home │ <ctrl> x 0x30478│ │0x30133 <ctrl> End │ <alt> x 0x100c1│ │0x30134 <ctrl-shft> PageUP │ <ctrl-shft> x 0x10018│ │0x30135 <ctrl-shft> PageDOWN │ <shft-alt> x 0x100d0│ │0x30136 <ctrl-shft> Home │ c 0x10063│ │0x30137 <ctrl-shft> End │ <shft> c 0x10043│ │0x30400 Insert │ <ctrl> c 0x30463│ │0x30401 <shft> Insert │ <alt> c 0x100d9│ │0x30402 <ctrl> Insert │ <ctrl-shft> c 0x10003│ │0x30403 <alt> Insert │ <shft-alt> c 0x100bd│ │0x30404 <shft-alt> Insert │ v 0x10076│ │0x30405 Delete │ <shft> v 0x10056│ │0x30406 <shft> Delete │ <ctrl> v 0x30476│ │0x30407 <ctrl> Delete │ <ctrl-shft> v 0x10016│ │0x30408 <alt> Delete │ b 0x10062│ │0x30409 <shft-alt> Delete │ <shft> b 0x10042│ │0x3040a BackSpace │ <ctrl> b 0x30462│ │0x3040b <shft> BackSpace │ <alt> b 0x100c8│ │0x3040c <ctrl-shft> Insert │ <ctrl-shft> b 0x10002│ │0x30461 <ctrl> a │ <shft-alt> b 0x100d4│ │0x30462 <ctrl> b │ n 0x1006e│ │0x30463 <ctrl> c │ <shft> n 0x1004e│ │0x30464 <ctrl> d │ <ctrl> n 0x3046e│ │0x30465 <ctrl> e │ <alt> n 0x100ca│ │0x30466 <ctrl> f │ <ctrl-shft> n 0x1001c│ │0x30467 <ctrl> g │ <shft-alt> n 0x100cf│ │0x30468 <ctrl> h │ m 0x1006d│ │0x30469 <ctrl> i │ <shft> m 0x1004d│ │0x3046a <ctrl> j │ <ctrl> m 0x3046d│ │0x3046b <ctrl> k │ <alt> m 0x100bc│ │0x3046c <ctrl> l │ <ctrl-shft> m 0x1000e│ │0x3046d <ctrl> m │ <shft-alt> m 0x100be│ │0x3046e <ctrl> n │ , 0x1002c│ │0x3046f <ctrl> o │ <shft> , 0x1003c│ │0x30470 <ctrl> p │ <alt> , 0x100dd│ │0x30471 <ctrl> q │ <shft-alt> , 0x100ae│ │0x30472 <ctrl> r │ . 0x1002e│ │0x30473 <ctrl> s │ <shft> . 0x1003e│ │0x30474 <ctrl> t │ <alt> . 0x100de│ │0x30475 <ctrl> u │ <shft-alt> . 0x100af│ │0x30476 <ctrl> v │ / 0x1002f│ │0x30477 <ctrl> w │ <shft> / 0x1003f│ │0x30478 <ctrl> x │ <shft-alt> / 0x100a8│ │0x30479 <ctrl> y │ SPACE 0x10020│ │0x3047a <ctrl> z │ Insert 0x30400│ │0x30800 Pad * │ <shft> Insert 0x30401│ │0x30801 <shft> Pad * │ <ctrl> Insert 0x30402│ │0x30802 Pad + │ <alt> Insert 0x30403│ │0x30803 <shft> Pad + │ <ctrl-shft> Insert 0x3040c│ │0x30804 Pad - │ <shft-alt> Insert 0x30404│ │0x30805 <shft> Pad - │ Delete 0x30405│ │0x30806 Pad / │ <shft> Delete 0x30406│ │0x30807 <ctrl> Pad + │ <ctrl> Delete 0x30407│ │0x3080b <ctrl-shft> Pad + │ <alt> Delete 0x30408│ │0x3080c Enter │ <shft-alt> Delete 0x30409│ │0x3080d <shft> Enter │ Home 0x30122│ │0x3080e <ctrl> Enter │ <shft> Home 0x30126│ │0x3080f <alt> Enter │ <ctrl> Home 0x30132│ │0x30810 Tab │ <ctrl-shft> Home 0x30136│ │0x30811 <left-shft> Tab │ End 0x30123│ │0x30812 <right-shft> Tab │ <shft> End 0x30127│ │0x30813 <right-ctrl-shft> Tab │ <ctrl> End 0x30133│ │0x30814 <ctrl> Tab │ <ctrl-shft> End 0x30137│ │0x30815 <left-ctrl-shft> Tab │ PageUP 0x30120│ │0x30820 Pad Enter │ <shft> PageUP 0x30124│ │0x30821 <shft> Pad Enter │ <ctrl> PageUP 0x30130│ │0x30822 <ctrl> Pad Enter │ <ctrl-shft> PageUP 0x30134│ │0x30823 <ctrl-shft> Pad Enter │ PageDOWN 0x30121│ │0x30824 <alt> Pad Enter │ <shft> PageDOWN 0x30125│ │0x30825 <shft-alt> Pad Enter │ <ctrl> PageDOWN 0x30131│ │0x30826 <ctrl-alt> Pad Enter │ <ctrl-shft> PageDOWN 0x30135│ │0x30827 <shft-ctrl-alt> Pad En│ ◄─ 0x30101│ │0x31000 F1 │ <left-shft> ◄─ 0x30105│ │0x31001 F2 │ <right-shft> ◄─ 0x30109│ │0x31002 F3 │ <ctrl> ◄─ 0x30109│ │0x31003 F4 │ <alt> ◄─ 0x3010d│ │0x31004 F5 │ <ctrl-shft> ◄─ 0x30115│ │0x31005 F6 │ <left-shft-alt> ◄─ 0x30111│ │0x31006 F7 │ <right-shft-alt> ◄─ 0x30113│ │0x31007 F8 │ ↑ 0x30102│ │0x31008 F9 │ <left-shft> ↑ 0x30106│ │0x31009 F10 │ <right-shft> ↑ 0x3010a│ │0x3100a F11 │ <ctrl> ↑ 0x3010a│ │0x3100b F12 │ <alt> ↑ 0x3010e│ │0x31010 <shft> F1 │ <ctrl-shft> ↑ 0x30116│ │0x31011 <shft> F2 │ <left-shft-alt> ↑ 0x30119│ │0x31012 <shft> F3 │ <right-shft-alt> ↑ 0x3011b│ │0x31013 <shft> F4 │ ─► 0x30103│ │0x31014 <shft> F5 │ <left-shft> ─► 0x30107│ │0x31015 <shft> F6 │ <right-shft> ─► 0x3010b│ │0x31016 <shft> F7 │ <ctrl> ─► 0x3010b│ │0x31017 <shft> F8 │ <alt> ─► 0x3010f│ │0x31018 <shft> F9 │ <ctrl-shft> -► 0x30117│ │0x31019 <shft> F10 │ <left-shft-alt> ─► 0x30112│ │0x3101a <shft> F11 │ <right-shft-alt> ─► 0x30114│ │0x3101b <shft> F12 │ ↓ 0x30104│ │0x31020 <ctrl> F1 │ <left-shft> ↓ 0x30108│ │0x31021 <ctrl> F2 │ <right-shft> ↓ 0x3010c│ │0x31022 <ctrl> F3 │ <ctrl> ↓ 0x3010c│ │0x31023 <ctrl> F4 │ <alt> ↓ 0x30110│ │0x31024 <ctrl> F5 │ <ctrl-shft> ↓ 0x30118│ │0x31025 <ctrl> F6 │ <left-shft-alt> ↓ 0x3011a│ │0x31026 <ctrl> F7 │ <right-shft-alt> ↓ 0x3011c│ │0x31027 <ctrl> F8 │ Pad / 0x30806│ │0x31028 <ctrl> F9 │ Pad * 0x30800│ │0x31029 <ctrl> F10 │ <shft> Pad * 0x30801│ │0x3102a <ctrl> F11 │ Pad - 0x30804│ │0x3102b <ctrl> F12 │ <shft> Pad - 0x30805│ │0x31040 <ctrl-shft> F1 │ Pad + 0x30802│ │0x31041 <ctrl-shft> F2 │ <shft> Pad + 0x30803│ │0x31042 <ctrl-shft> F3 │ <ctrl> Pad + 0x30807│ │0x31043 <ctrl-shft> F4 │ <ctrl-shft> Pad + 0x3080b│ │0x31044 <ctrl-shft> F5 │ Pad Enter 0x30820│ │0x31045 <ctrl-shft> F6 │ <shft> Pad Enter 0x30821│ │0x31046 <ctrl-shft> F7 │ <ctrl> Pad Enter 0x30822│ │0x31047 <ctrl-shft> F8 │ <alt> Pad Enter 0x30824│ │0x31048 <ctrl-shft> F9 │ <ctrl-shft> Pad Enter 0x30823│ │0x31049 <ctrl-shft> F10 │ <shft-alt> Pad Enter 0x30825│ │0x3104a <ctrl-shft> F11 │ <ctrl-alt> Pad Enter 0x30826│ │0x3104b <ctrl-shft> F12 │ <shft-ctrl-alt> Pad Ent 0x30827│ └───────────────────────────────┴─────────────────────────────────┘ Notice that Pad 0 through Pad 9 (also <shft> Pad 0 to 9, and <alt> Pad 0 to 9) are not included in this list. The reason is that these keystrokes are still needed by Dmuse for changing windows. Consequently, they are not available to Zbex. 21.13 There is also a getx instruction in Zbex. This instruction is different from getk in that it does not block the execution of the Zbex program. If there has been one or more keystrokes (from the set above) that has not yet been processed, and the Zbex interpreter encounters a getx instruction, then the value of the most recent keystroke will be delivered, and the keystroke queue will be cleared. If the keystroke queue is empty, a getx instruction will simply return 0x0000 in the output variable. The Zbex program will continue to execute. 21.14 Zbex provides three commands for scaling a graphics image: dscale2, which produces a 1/2 size image, dscale3, which produces a 1/3 size image, and dscale5, which produces a 2/3 size image. A common use of these instructions is scaling an image, constructed at 300 dots/inch (for printing), down to a size that can viewed on a screen (e.g., 75 dots/inch). There are two forms for these instructions: (1) complete scaling and (2) partial scaling. Both forms require two graphic string variables, a source and a destination. At compile time, any simple strings will suffice. It is at run time that the program must check values to see that the strings are properly formatted (setup) as graphics buffers. In particular, the following conditions for the string variables must hold: For both strings, 0 < m*n*p <= len(S-str) - 20, where m = value represented in bytes 1-2 (BYTES in row) n = value represented in bytes 3-4 (number of rows) p = value represented in bytes 5 (number of planes) It is possible to specify four additional integer parameters to the dscale instructions. These are respectively the left boundary, the top boundary, the right boundary, and the bottom boundary of the space that is to be scaled. Left and right boundaries are measured in COLUMNS; top and bottom boundaries are measured in ROWS. If these variables are not specified, the values <0,0> and <90000,90000> are supplied by the compiler. The format for the dscale instructions is then: dscale3 S-str1, S-str2 or dscale3 S-str1, S-str2, int,int,int,int Operation (for the instruction dscale3): Let m1 = value for m for S-str1; and m2 = value of m for S-str2 Let n1 = value for n for S-str1; and n2 = value of n for S-str2 If m1 = 3 * m2, rows will be mapped directly from S-str1 to S-str2 (three rows to one). If m1 > 3 * m2, scaled rows from S-str1 will be concatinated to fit in S-str2. If m1 < 3 * m2, scaled rows from S-str1 will be padded with zero bytes to fill up S-str2. If n1 = 3 * n2, all rows will be scaled directly from S-str1 to S-str2 (three rows to one). If n1 > 3 * n2, only a sufficent number of rows from S-str1 will be scaled to S-str2 to fill it up. If n1 < 3 * n2, all rows from S-str1 will be scaled directly to S-str2. The remaining rows will be set to zero bytes. In dealing with the space to be scaled, the COLUMN values are converted (expanded outward) to the nearest 3-BYTE boundaries, and the row values are expanded outward to the nearest 3-ROW boundaries. The minimum values for <BTYES,ROWS> are adjusted to be no less than <0,0> and the maximum values for <BTYES,ROWS> are adjusted to no greater than <m1,n1>. Then, only those rows and bytes between the minimum and maximum values are scaled. In describing the operation for dscale2, all values of 3 should be replaced by 2. For the dscale5 instruction, all values of 3 would be replaced by 3/2. 21.15 In a Zbex program that uses graphics mode, certain run-time situations can occur whose behavior needs to be specified. 1. If graphics instructions, activate, setb, or clearb, are encountered before the first execution of the bitmode instruction, this is not a problem. The graphics screen is initialized, but the user's Zbex program will not know the size of the user's X-Window display. If one of these instructions is encountered while Zbex is running in textmode, then the mode will be changed to graphics mode, and the graphics screen restored. Remember, using the bitmode instruction to change to graphics mode will clear the graphics screen. 2. textmode may be executed any time. If Zbex is in graphics mode at the time, then textmode will cause the graphics screen to be temporarily stored. 3. If a Zbex program is running in graphics mode, it may be temporarily suspended by changing to another window. It will be terminated if "disconnected" (<shft> KeyPad *) in the same window. If a program with graphics is suspended, it will not execute in the background. Also, when a program with graphics applications is running, all other Zbex programs are suspended. This is because the getk instruction preempts the event loop. 4. If getc or putc is encountered while Zbex is running in graphics mode, the effect will be the same as if a textmode instruction were executed and then the getc or putc. This allows the seamless (although probably not instantaneous) transition from graphics to text and back (see 1 above). ────────────────────────────────────────────────────────────────── ┌──────────────────────────────────────────┐ │ 22. MIDI instructions (Advanced Topic) │ └──────────────────────────────────────────┘ 22.1 MIDI stands for Musical Instrument Digital Interface. MIDI is the dominant method for sending data to and receiving data from a music keyboard. In order for Dmuse running on Linux to interface with a MIDI keyboard, your system needs two things. 22.2 (1) You need to have the ALSA (Advanced Linux Sound Architecture) Driver installed on your system. You can determine whether the ALSA system is installed on your computer by typing the command: cat /proc/asound/version which should return something like: Advanced Linux Sound Architecture Driver Version 1.0.17 If you do not get positive response to the command above, then you will need to go on line and download the Alsa system. The web site for the ALSA project is www.alsa-project.org You will need to download the alsa-lib and the alsa-utils and you will need a makefile, which looks something like this: all: unpack compile download: ┊wget -i source.txt unpack: ┊tar xvjf alsa-lib-1.0.20.tar.bz2 ┊tar xvjf alsa-utils-1.0.20.tar.bz2 compile: ┊(cd alsa-lib-1.0.20; ./configure) ┊(cd alsa-lib-1.0.20; make install) -------------------------------------------------- Note: ┊ = Tab character (2) You need to have hardware and drivers that connect your computer (probably via a usb port) to a keyboard. I am using a MidiMan 1x1 usb MIDI device. To get and install the drivers, following the instructions on http://usb-midi-fw.sourceforge.net (1) Download fxload RPM package: wget ftp://rpmfind.net/linux/fedora/updates/7/i386/fxload-2002_04_11-6.fc7.i386.rpm (2) Install fxload rpm: --> rpm --install fxload-2002_04_11-6.fc7.i386.rpm (3) Download midisport firmware package: wget http://softlayer.dl.sourceforge.net/sourceforge/usb-midi-fw/midisport-firmware-1.2.tar.gz --> (4) tar xvzf midisport-firmware-1.2.tar.gz --> (5) cd midisport-firmware-1.2 --> (6) ./configure --> (7) make (8) As root, type: --> make install (9) Now, when you plug in a MidiSport 1x1 USB device on the computer the green LED near the USB output should start pulsing, which indicates that it is activated properly and ready to use with the ALSA system. You can check that you have ALSA installed on your system by running this command: cat /proc/alsa/version Which will reply something like this: Advanced Linux Sound Architecture Driver Version 1.0.14 (Thu May 31 09:03:25 2007 UTC). 22.3 When you have done these things, you need to give Dmuse the information it needs to talk to the MIDI port. This information is transmitted in your INIT file in Record 48. When Dmuse is first installed, this record has an "N" in column 17. This must be changed to a "Y", indicating that MIDI has been installed. There must be a space in column 18. This is followed by the port name and the name of the hardware you are using. The length of the hardware name need only be long enough to identify the hardware uniquely. The port name and the hardware name must be separated by a semi-colon, and must be in double quotes. Dmuse also needs to know the path location of the "midi_in" program. On my system, Record 48 of the INIT file looks like this: MIDI Installed: Y "hw:1,0,0;MidiSport" "/usr/local/apps/disp" Craig Stuart Sapp has written a program called alsarawportlist which should be in the same path location as the program midi_in. If you run this program, and if the ALSA system is installed on your computer, You will get a list of MIDI devices and hardware names on your computer, from which you can construct Record 48 in your INIT file. 22.3 If your MIDI interface has been installed properly, you can communicate with a music keyboard using the following five Zbex MIDI instructions: midistart midistop midiget str midiput str-exp miditime int Before explaining these instructions, it should be pointed out that Dmuse can communicate with MIDI though only one Zbex program at a time. If another Zbex program (running in another window) has any midi instructions whatsoever, executing or not, a subsequent Zbex program containing any midi instructions will compile, but will not run. Instead, it will exit with the message: Unable to run program. Midi in use by another zbex program. 22.4 A. midistart Format: midistart stands alone and takes no arguments. Operation: Midistart is the command that sets up the operation of the MIDI interface. No other midi commands will execute unless and until the midistart command has been issued. Midistart starts the MIDI timer and clears the MIDI I/O buffers and opens the snd_rawmidi write interface. Midistart checks to see if midi is "plugged in." If you are using a usb MidiSport, for example, you might have everything installed on your system, but the MidiSport might be unplugged. In this case, midistart will exit with the message: Unable to open the midi interface at line = xxx. Check to see if the midi interface is connected. Wait 10 seconds after connecting. Midistart actually has two modes of execution depending on whether or not the Zbex program contains the midiget instruction. Without the midiget instruction, MIDI operation is relatively simple. MIDI data is sent out to the interface whenever your program executes a midiput instruction, and you can select when to do this by checking the MIDI timer with the miditime instruction. With midiget, however, things get more complicated. Dmuse needs to fork, creating a child process, which executes a separate program called "midi_in." When started, midi_in simply "listens" for input from the music keyboard (or other MIDI device) and passes time-stamped data back to Dmuse through a FIFO pipe. If your MIDI interface becomes disconnected, or midi-in from the keyboard stops working for any reason, it becomes impossible for Dmuse to talk to the child process (actually, the child process stops listening for commands from Dmuse). Under these conditions, if Dmuse tries to stop the child (with a midistop instruction), this will freeze the entire Dmuse environment (as presently coded). This is why we check first to see that the MIDI interface is plugged in before we startmidi. Note: If you are an experienced Linux user, you can unfreeze Dmuse by using the ps command to identify the process number of the child process running "midi_in." You can then use the kill command to terminate "midi_in," which will cause a "pipe-closed" signal to be sent to Dmuse and allow Dmuse to complete the midistop procedure. 22.5 B. midistop Format and Operation: The midistop command takes no argument. Operation depends on the current running state of the Zbex program. If the MIDI interface is currently active, the midistop command begins by closing the snd_rawmidi write interface. If the Zbex program does not contain the midiget instruction (i.e., was not opened for midiget), this completes the stop operation. If Zbex was open for midiget, then these addtional steps must be completed. (1) Close the FIFO pipe Dmuse -> midi_in. This tells the midi_in program (running in the child process) that MIDI operations are to be stopped. (2) Wait for midi_in to acknowledge this message. (Midi_in does this by closing its end of the midi_in -> Dmuse pipe.) This step also insures that the child process (midi-in) is terminated. (3) Close the FIFO pipe (read end of) midi_in -> Dmuse. (4) Remove the process id for the child process. Note: this sequence can be fouled up if the MIDI_IN connection from the MIDI device to the computer is broken. A broken connection will cause the midi_in program (running in the child process) to get stuck in the loop which looks for input from the MIDI device. Since it is stuck there, it cannot check for a close-pipe signal from Dmuse. Consequently, Dmuse will hang on step (2) of the above sequence. If Dmuse simply skips step (2) after a timeout, then the child process is left spinning and is never terminated, and step (4) creates a "zombie." midistop does not generate error messages. 22.6 C. midiget Format: midiget takes one argument: an string variable (without subscripts) Operation: midiget is used to read MIDI data that has come in from the MIDI interface. The data is actually read by the independent program midi_in, running as a child process. Midi_in reads MIDI data as it comes in and appends a time stamp to each midi packet (which consists of a midi command and the proper number of data bytes). If the MIDI interface is not currently activated for midiget (with the midistart instruction), the Zbex program is terminated with an error message. Otherwise Zbex attempts to read "atomic" events from the (circular) pipe buffer. An atomic event consists of a four byte time stamp, plus a complete midi packet. Since midi_in "write events" to the FIFO pipe are asynchronous with the Dmuse Zbex interpreter, we cannot be certain that the data present in the pipe buffer at any particular time is complete. Therefore a read (and removal) from the pipe buffer is done only when a complete "atomic" event is found. If no complete atomic events are found, midiget returns the null string. Otherwise, midiget will return one or more complete atomic events in the output string, provided there is space available in the string. If adding an atomic event to the output string would cause its maximum length to be exceeded, the atomic event is not read (not removed from the circular buffer). An atomic event should always start with an 0xff byte. If this is not the case, a flag is set, and all further data is assumed to be corrupted. In this event, all further calls to midiget will simply return the null string. The data_integrety_flag is reset when MIDI is stopped. The time stamp consists of four bytes: 0xff + a 3 byte number. (maximum value = 16,777,215) The midi_in "timer" is started when the midistart command is executed. Timer units are in milliseconds. (16,777,215 milliseconds is approximately 4 and 2/3 hours). From the standpoint of collecting data, it is the difference in time stamps that matters, not the absolute value. The 4 hour upper limit on time stamp should not present a logical problem to any running Zbex program. A midi packet consists of a midi command (one byte) and from 0 to 2 data bytes, depending on the command. Midiget will read as many atomic events as permitted by the size of the input string, and if possible all atomic events in the (circular) pipe buffer. 22.7 D. midiput Format: midiput takes one argument: a string expression (subscripts allowed) Operation: midiput is used to send MIDI data directly to the MIDI interface with no time delay. If the MIDI interface is not currently activated (with the midistart instruction), the Zbex program is terminated with an error message. Otherwise, contents of the string is transferred to the (non-circular) MIDI-out buffer, space permitting. Cycling of the Zbex program is then stopped and control is returned to the Dmuse main module, which "writes" the contents of the MIDI-out buffer to the MIDI interface and resets the buffer pointers. 22.8 E. miditime Format: miditime takes one integer variable as vehicle for input. Operation: One of the things midistart does when it is first called is to read the time-of-day. A call to miditime also reads the time-of-day. A difference is calculated and the number of transpired milliseconds is computed. This number is returned in the integer variable. If miditime is called without first calling midistart, the Zbex program is terminated with an error message. Use of miditime: Miditime is used in conjunction with midiput to send music (MIDI commands) out to the midi interface. A MIDI file consists of a series of MIDI commands, each one preceded by a time delay (which can be zero). A the top of a MIDI file are two numbers: the number of divisions per quarter note (e.g. 240), and the tempo number (measured as the number of u-secs per quarter note). The time delay numbers are measured in divisions. Typically, the number of divisions per quarter note does not change in a MIDI file. The tempo number, however, can be changed. This is how tempos are changed in a MIDI performance. In order for a Zbex program to send out MIDI commands at the proper time, there needs to be a delay procedure which can "spin" for a specified length of time (i.e., a specified interval called a division or a "tick." And what is a "tick" in terms of milliseconds? Well this depends on the tempo. If the tempo is 60 quarters per minute, which is one quarter per 1,000 milli-secs, and there are 240 divisions in a quarter, then one division is 1,000 / 240 = 4.16667 milli-secs. Since this unit contains a significant rounding error (to the nearest integer), we cannot use it directly, but must work with a cumulative counter. We define the procedure tick as follows: procedure tick int i,j,k ++midi_ticks k = midi_ticks * 2000 / tempo_mult loop miditime i if i > k return end repeat return where tempo_mult is set as follows, assuming the string temp{1,6} contains the tempo setting MIDI command, and divspq = number of divisions per quarter note: if temp{1} = chr(255) and temp{2} = chr(0x51) s4 = temp{3,4} <-- n-sec. per quarter usec_pq = ors(s4) h = 2000000 * divspq / usec_pq putc ~h divisions per (2 seconds) tempo_mult = h temp = temp{7..} end Note that this code resolves the tempo_mult variable to the number of divisions per (2 second), which gives us some extra resolution in terms of tempo variances. In this scheme, the midi_ticks "clock" keeps increasing, so fractions parts of the fundamental unit (division) also accumulate. And since most time intervals that we can detect are in the range of 10 or more divisions, the resolution error goes undetected. The "spin" portion of the procedure simply makes repeated calls to miditime until the "timer" exceeds the target. And the tick procedure is called in the main program for the exact number divisions that are requested as a time delay. Note: This code only works when the tempo is constant. A change in the tempo number will change the tempo_mult variable and completely screw-up the value of k in the procedure tick. In order to fix this problem, the value of k would need to be the sum of a "total elapsed time at the point where a new tempo is set" plus "the increment since that setting." You write the code! ──────────────────────────────────────────────────────────────────