NCL 211: Working with Text
Over the last several lessons, we've collected quite a few tools for working with text.
We can store strings, change them, inspect them, take them apart, compare them, build new ones, and even include characters that change how the Display presents them.
This time, we're not going to introduce another string operation.
Instead, we're going to put several of the ones we already know to work together.
A little provided code
We'll start with this program:
#device s0
#status s1
MOVE r31 0; JUMP $next
$case0
SMOVE #device "printer"; SMOVE #status "ready"; INC r31; JUMP $format
$case1
SMOVE #device "network"; SMOVE #status "warning"; INC r31; JUMP $format
$case2
SMOVE #device "drive"; SMOVE #status "error"; INC r31; JUMP $format
$case3
SMOVE #device "display"; SMOVE #status "offline"; INC r31; JUMP $format
$next
BEQ $case0 r31 0; BEQ $case1 r31 1; BEQ $case2 r31 2; BEQ $case3 r31 3; D.COL 15 0 0; JUMP 0
$format
-- Your code here.
JUMP $next
There's quite a bit packed into the top of this program, but there isn't anything new hiding in it.
It uses registers, aliases, MOVE, SMOVE, INC, BEQ, JUMP, labels, and coalesced instructions. We've worked with all of those before.
For this lesson, however, we don't need to worry about the details.
The provided code will reach $format four times.
Each time it does, it puts a new pair of strings into these registers:
#device
#status
Then our formatting code runs.
When it reaches:
JUMP $next
the provided code prepares another pair and sends execution through $format again.
The four pairs are:
| Pass | #device |
#status |
|---|---|---|
| 1 | "printer" |
"ready" |
| 2 | "network" |
"warning" |
| 3 | "drive" |
"error" |
| 4 | "display" |
"offline" |
We'll leave the provided code alone and work below $format.
Start by displaying the values
Let's begin with the simplest formatter we can make.
Replace:
-- Your code here.
with:
D.TXT #device
D.TXT " "
D.TXT #status
D.TXT "\r\n"
Our formatter is now:
$format
D.TXT #device
D.TXT " "
D.TXT #status
D.TXT "\r\n"
JUMP $next
Run the program.
The Display shows:
printer ready
network warning
drive error
display offline
We only wrote the formatting code once.
The provided code changed the contents of #device and #status, then sent execution through the same formatter each time.
Our formatter doesn't need to know which pass is running.
It simply works with whatever values are currently in those registers.
Normalize the text
The output works, but we'd like the device and status names to be uppercase:
PRINTER READY
NETWORK WARNING
DRIVE ERROR
DISPLAY OFFLINE
We already know an operation that can do that.
Add these instructions at the beginning of $format:
SUPR #device #device
SUPR #status #status
Our formatter becomes:
$format
SUPR #device #device
SUPR #status #status
D.TXT #device
D.TXT " "
D.TXT #status
D.TXT "\r\n"
JUMP $next
Now the same formatting code transforms each pair before displaying it.
Notice that we're changing #device and #status directly.
That's fine here.
Once we're finished with the current pair, the provided code replaces both registers with the values for the next pass.
Line up the statuses
Our uppercase output is easier to read, but the statuses begin in different places:
PRINTER READY
NETWORK WARNING
DRIVE ERROR
DISPLAY OFFLINE
Let's give the device name a fixed-width field.
We used SPADR in NCL 206 to extend a string on the right until it reaches a requested length.
Add:
SPADR #device #device 12 " "
after converting the device name to uppercase:
$format
SUPR #device #device
SUPR #status #status
SPADR #device #device 12 " "
D.TXT #device
D.TXT #status
D.TXT "\r\n"
JUMP $next
We no longer need the separate space between the two fields. The spaces are now part of #device.
The result is:
PRINTER READY
NETWORK WARNING
DRIVE ERROR
DISPLAY OFFLINE
The same three operations prepare every pair:
| Value | Before | After |
|---|---|---|
#device |
"printer" |
"PRINTER " |
#status |
"ready" |
"READY" |
The other passes go through exactly the same transformations.
Give READY a colour
Now let's make the status itself communicate a little more information.
We want:
READYdisplayed normally in green;WARNINGdisplayed normally in yellow;ERRORdisplayed inverted in red;- anything else displayed normally in white.
We'll start with READY.
Our status is already uppercase by the time we need to make this decision.
That means we can compare it directly with:
"READY"
Add this after preparing the strings:
BSEQ $ready #status "READY"
Then add a $ready path:
$ready
D.TXT #device
D.TXT "\s04"
D.TXT #status
D.TXT "\s0F\r\n"
JUMP $next
Palette entry 4 is green, so:
\s04
selects normal green text.
The final:
\s0F
restores the normal Display attributes.
We still need somewhere for all the statuses that aren't READY to go. For now, they can use our original output:
$format
SUPR #device #device
SUPR #status #status
SPADR #device #device 12 " "
BSEQ $ready #status "READY"
D.TXT #device
D.TXT #status
D.TXT "\s0F\r\n"
JUMP $next
$ready
D.TXT #device
D.TXT "\s04"
D.TXT #status
D.TXT "\s0F\r\n"
JUMP $next
Run it again.
All four lines still appear, but READY is now green.
We haven't changed the provided code at all. We've changed what our formatter does when it receives one particular value.
Add WARNING
Now we want to recognize another status.
We've built this kind of multiple-choice path before.
Before falling through to the ordinary white output, add another comparison:
BSEQ $ready #status "READY"
BSEQ $warning #status "WARNING"
Then give WARNING its own path:
$warning
D.TXT #device
D.TXT "\s0D"
D.TXT #status
D.TXT "\s0F\r\n"
JUMP $next
Palette entry D is yellow, so WARNING is displayed normally in yellow.
Our formatter can now distinguish between three possibilities:
READYtakes the$readypath.WARNINGtakes the$warningpath.- Anything else falls through to the ordinary white output.
This is the same chained-decision pattern we used back in NCL 109.
We're simply using it with strings now.
Add ERROR
There's one more special status in our input:
ERROR
This time, we'll make it inverted red.
We already know the pattern.
Add another BSEQ to the chain:
BSEQ $ready #status "READY"
BSEQ $warning #status "WARNING"
BSEQ $error #status "ERROR"
Then add an $error path.
Try writing this one yourself before continuing.
You'll need to:
- display
#device; - change to inverted red;
- display
#status; - restore the Display attributes;
- move to the next line;
- jump back to
$next.
In NCL 210, we used:
\s1X
for inverted text using palette entry X.
Red is palette entry 1.
So one solution is:
$error
D.TXT #device
D.TXT "\s11"
D.TXT #status
D.TXT "\s0F\r\n"
JUMP $next
Run the program again.
We now have four differently handled inputs:
| Status | Presentation |
|---|---|
READY |
Green |
WARNING |
Yellow |
ERROR |
Inverted red |
| Anything else | Normal white |
OFFLINE doesn't have its own path.
That's intentional.
None of the three BSEQ instructions match it, so execution falls through to our ordinary output.
The complete formatter
Our part of the program now looks like this:
$format
SUPR #device #device
SUPR #status #status
SPADR #device #device 12 " "
BSEQ $ready #status "READY"
BSEQ $warning #status "WARNING"
BSEQ $error #status "ERROR"
D.TXT #device
D.TXT #status
D.TXT "\s0F\r\n"
JUMP $next
$ready
D.TXT #device
D.TXT "\s04"
D.TXT #status
D.TXT "\s0F\r\n"
JUMP $next
$warning
D.TXT #device
D.TXT "\s0D"
D.TXT #status
D.TXT "\s0F\r\n"
JUMP $next
$error
D.TXT #device
D.TXT "\s11"
D.TXT #status
D.TXT "\s0F\r\n"
JUMP $next
There isn't a new instruction anywhere in it.
We used:
SUPRto normalize text;SPADRto construct a fixed-width field;BSEQto choose between string values;- labels and
JUMPto arrange different paths; D.TXTto build our displayed output;\sXXcharacters to change its presentation;\r\nto begin another line.
Those pieces came from several different lessons.
Here, they're working together to solve one problem.
What the provided code was doing
We didn't need to understand the provided code in detail to write the formatter.
But by now, it may look less mysterious than it did at the beginning.
Consider one of its lines:
SMOVE #device "network"; SMOVE #status "warning"; INC r31; JUMP $format
Read it from left to right.
It:
- puts
"network"in#device; - puts
"warning"in#status; - increments
r31; - jumps to
$format.
Our code works with those values and eventually executes:
JUMP $next
At $next, the provided code checks r31:
BEQ $case0 r31 0; BEQ $case1 r31 1; BEQ $case2 r31 2; BEQ $case3 r31 3; D.COL 15 0 0; JUMP 0
The counter determines which pair should be loaded next.
When there are no more pairs, none of the branches are taken. The Display attributes are restored, and execution moves beyond the program.
The supplied code is dense, but its individual pieces aren't unfamiliar.
It's the same registers, comparisons, branches, labels, and jumps we've been using throughout the course.
Try it
Now that the formatter works, change it.
Perhaps OFFLINE deserves its own appearance.
Add another comparison:
BSEQ $offline #status "OFFLINE"
Choose a colour or inversion for it yourself, then add the corresponding path.
Or change the width of the device field:
SPADR #device #device 16 " "
and see how the output changes.
You can also change one of the test cases in the provided code. For example:
SMOVE #device "keyboard"; SMOVE #status "connected"; INC r31; JUMP $format
Your formatter has never seen either of those values before.
It doesn't need to.
It still converts them to uppercase, pads the device field, and displays the unknown status normally:
KEYBOARD CONNECTED
Try adding your own status and deciding whether it needs special presentation.
In this lesson, we didn't learn another operation.
We started with two values and gradually decided what our program should do with them. Each improvement used tools we'd already learned.
As programs become larger, this becomes increasingly important. A new problem doesn't always need a new instruction. Often, the pieces you already have can be combined in a new way.
So far, however, all of our programs have already had the values they needed.
In NCL 301: Reading the Keyboard, we'll start getting those values from the person using the computer.