╔════════════════════════════════════════╗
║ ║
║ 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!
──────────────────────────────────────────────────────────────────