To develop a capability to write text on a bitmap screen, we first have to divide the physical pixel-oriented screen into a logical, character-oriented screen suitable for writing complete characters. For example, consider a screen that is 256 rows by 512 columns. If we allocate a grid of 11 * 8 pixels for drawing a single character (11 rows, 8 columns), then our screen can show 23 lines of 64 characters each (with 3 extra rows of pixels left unused).
Next, for each character that we want to display on the screen, we can design a good-looking font, and then implement the font using a series of character bitmaps. For example, figure 12.10 gives a possible bitmap for the letter ‘A’.
Figure 12.9
Circle drawing.
Note that in order for our display scheme to account for the requisite inter-character spacing, we must make sure that the 11 * 8 bitmap of each character includes at least a 1-pixel space before the next character and at least a 1-pixel space between adjacent lines (the exact spacing may vary with the size of the individual characters).
Characters are usually drawn on the screen one after the other, from left to right. For example, the two commands print(“a”) and print(“b”) probably mean that the programmer wants to see the image “ab” drawn on the screen. Thus the character-writing package must maintain a “cursor” object that keeps track of the screen location where the next character should be drawn. The cursor information consists of line and column counts. For example, the character screen described at the section’s beginning is characterized (excuse the pun) by 0 ≤
line
≤ 22 and 0 ≤
column
≤ 63. Drawing a single character at location (
line
, column) is achieved by writing the character’s bitmap onto the box of pixels at rows line · 11 ...line · 11 + 10 and columns
column
· 8 ...
column
· 8 + 7. After the character has been drawn, the cursor should be moved one step to the right (i.e.,
column
= column + 1), and, when a new line is requested, row should be increased by 1 and column reset to 0. When the bottom of the screen is reached, there is a question of what to do next, the common solution being to effect a “scrolling” operation. Another possibility is starting over at the top left corner, namely, setting the cursor to (0,0).
Figure 12.10
Character bitmap of the letter “A”.
To conclude, we know how to write characters on the screen. Writing other types of data follows naturally from this basic capability: strings are written character by character, numbers are first converted to strings and then written as strings, and so on.
Keyboard Handling
Handling user-supplied text input is more involved than meets the eye. For example, consider the command name=readLine ( “enter your name:”). The low-level implementation of this command is not trivial, since it involves an unpredictable event: A human user is supposed to press some keys on the keyboard before this code can terminate properly. And the problem, of course, is that human users press keyboard keys for variable durations of time. Hence, the trick is to encapsulate the handling of all these messy low-level details in OS routines like readLine, freeing high-level programs from this tedium.
Figure 12.11
Capturing “raw” keyboard input.
This section describes how the operating system manages text-oriented input in three increasing levels of abstraction: (i) detecting which key is currently pressed on the keyboard, (ii) capturing single-character inputs, and (iii) capturing multi-character inputs, that is, strings.
Detecting Keyboard Input
In the lowest-level form of capturing keyboard input, the program gets data directly from the hardware, indicating which key is currently pressed by the user. The access to this raw data depends on the specifics of the keyboard interface. For example, if the interface is a memory map that is continuously refreshed from the keyboard, as in Hack, we can simply inspect the contents of the relevant RAM area to determine which key is presently pressed. The details of this inspection can then be incorporated into the implementation of the algorithm in figure 12.11.
For example, if you know the RAM address of the keyboard memory map in the host computer, the implementation of this algorithm entails nothing more than a memory lookup.
Reading a Single Character
The elapsed time between “key pressed” and “key released” events is unpredictable. Hence, we have to write code that neutralizes this variation. Also, when users press keys on the keyboard, we usually want to give a visual feedback as to which keys have been pressed (something that you have probably grown to take for granted). Typically, we want to display some graphical cursor at the screen location where the next input “goes” and, after some key has been pressed, we typically want to echo the inputted character by displaying its bitmap on the screen at the cursor location. This logic is implemented in figure 12.12.
Reading a String
Usually, a multi-key input typed by the user is considered final only after the enter key has been pressed, yielding the newline character. And, until the enter key is pressed, the user should be allowed to backspace and erase previously typed characters. The code that implements this logic and renders its visual effect is given in figure 12.13.
Figures 12.12 and 12.13
Capturing “cooked” keyboard input.
As usual, our input handling solutions are based on a cascading series of abstractions: The high-level program relies on the readLine abstraction, which relies on the readChar abstraction, which relies on the keyPressed abstraction, which relies on the hardware.
12.2 The Jack OS Specification
The previous section presented a series of algorithms that address some classic operating system tasks. In this section we turn to formally specify one particular operating system—the Jack OS—in API form. Since the Jack OS can also be viewed as an extension of the Jack programming language, this documentation duplicates exactly “The Jack Standard Library” from section 9.2.7. In chapter 9, the OS specification was intended for programmers who want to use its abstract services; in this chapter, the OS specification is intended for programmers who have to implement these services. Technical information and implementation tips follow in section 12.3.
The operating system is divided into eight classes:
•
Math:
provides basic mathematical operations;
•
String:
implements the String type and string-related operations;
•
Array:
implements the Array type and array-related operations;
•
Output:
handles text output to the screen;
•
Screen:
handles graphic output to the screen;
•
Keyboard:
handles user input from the keyboard;
•
Memory:
handles memory operations;
•
Sys:
provides some execution-related services.
12.2.1 Math
This class enables various mathematical operations.
• function void
init
(): for internal use only;
• function int
abs
(int x): returns the absolute value of x;
• function int
multiply
(int x, int y): returns the product of x and y;
• function int
divide
(int x, int y): returns the integer part of x/y;
• function int
min
(int x, int y): returns the minimum of x and y;
• function int
max
(int x, int y): returns the maximum of x and y;
• function int
sqrt
(int x): returns the integer part of the square root of x.
12.2.2 String
This class implements the String data type and various string-related operations.
■ constructor String
new
(int maxLength): constructs a new empty string (of length zero) that can contain at most maxLength characters;
■ method void
dispose
(): disposes this string;
■ method int
length
(): returns the length of this string;
■ method char
charAt
(int j): returns the character at location j of this string;
■ method void
setCharAt
(int j, char c): sets the j-th element of this string to c;
■ method String
appendChar
(char c): appends c to this string and returns this string;
■ method void
eraseLastChar
(): erases the last character from this string;
■ method int
intValue
(): returns the integer value of this string (or the string prefix until a non-digit character is detected);
■ method void
setInt
(int j): sets this string to hold a representation of j;