NCL 301: Reading the Keyboard

Until now, every value our programs worked with was already in the program.

We wrote the numbers.

We wrote the strings.

The CPU followed our instructions using those values.

Now we'll let the person using the computer provide a value instead.

We'll start with the smallest possible kind of input:

one key.

Reading a key

Pressed keys are kept in a queue until the program reads them.

SYS.KEY reads the oldest key currently waiting in that queue.

Let's try it:

D.TXT "PRESS A KEY: "
D.BLT

SYS.KEY s0

D.TXT s0
D.BLT

Run the program without pressing anything first.

You may see:

PRESS A KEY:

and nothing after it.

That's because SYS.KEY does not wait for a key.

If the keyboard queue contains a key, SYS.KEY places that key in the destination string register.

If the queue is empty, it places an empty string there instead:

""

So when execution reached:

SYS.KEY s0

there may simply have been nothing waiting to read.

Checking repeatedly

We could solve that by checking the keyboard again whenever the queue is empty.

We already know how to repeat work with a loop:

D.TXT "PRESS A KEY: "
D.BLT

$wait
SYS.KEY s0
BSEQ $wait s0 ""

D.TXT s0
D.BLT

Now follow the program.

Execution reaches:

SYS.KEY s0

If no key is waiting:

s0 = ""

Then:

BSEQ $wait s0 ""

takes the branch and checks again.

Still nothing?

It checks again.

And again.

And again.

Eventually, you press a key.

Suppose you press:

K

Now:

s0 = "K"

The comparison with "" fails, execution continues, and the Display shows:

PRESS A KEY: K

This works.

But while we're sitting there thinking about which key to press, the CPU is repeatedly asking:

Is there a key yet?

Is there a key yet?

Is there a key yet?

Most of the time, the answer is no.

Waiting instead

For cases where we know we need a key before anything useful can happen, NCL provides another operation:

SYS.AKEY s0

SYS.AKEY reads the keyboard queue just like SYS.KEY.

The difference appears when the queue is empty.

Operation Key waiting Queue empty
SYS.KEY Returns the oldest key Returns ""
SYS.AKEY Returns the oldest key Waits until a key is available

That lets us replace our polling loop with:

D.TXT "PRESS A KEY: "
D.BLT

SYS.AKEY s0

D.TXT s0
D.BLT

Run it again.

This time, if no key is waiting when execution reaches:

SYS.AKEY s0

execution waits there.

Press a key, and the operation completes.

The key is placed in s0, and execution continues.

If a key was already waiting in the keyboard queue, SYS.AKEY would return it immediately.

Following a waiting program

This is a little different from the programs we've traced before.

Consider:

D.TXT "PRESS A KEY: "
D.BLT

SYS.AKEY s0

D.TXT s0
D.BLT

You can still follow execution with your finger.

First:

D.TXT "PRESS A KEY: "

puts the prompt in the Display buffer.

Then:

D.BLT

makes it visible.

Then your finger reaches:

SYS.AKEY s0

and stops.

There is no next instruction to execute yet.

The program is waiting for something outside itself to happen.

Press:

A

Now s0 contains:

"A"

and your finger can continue to:

D.TXT s0

For the first time, the value our program is working with was chosen by the person using it.

Hit Y to continue

Reading any key is useful, but often we care about one particular key.

Suppose we want:

HIT Y TO CONTINUE

Pressing anything else should keep waiting.

We can combine SYS.AKEY with the string comparisons we already know:

D.TXT "HIT Y TO CONTINUE"
D.BLT

$wait
SYS.AKEY s0
BSNEQ $wait s0 "Y"

D.TXT "\r\nCONTINUING..."
D.BLT

Run it.

Press:

X

SYS.AKEY places "X" in s0.

Then:

BSNEQ $wait s0 "Y"

asks whether "X" is not equal to "Y".

It isn't, so execution jumps back to $wait.

The program waits again.

Press:

Y

Now s0 contains "Y".

This time the comparison is false, so the branch is not taken.

Execution continues:

D.TXT "\r\nCONTINUING..."
D.BLT

and the Display shows:

HIT Y TO CONTINUE
CONTINUING...

The program didn't know when you would press Y.

It simply kept waiting until you did.

What about lowercase y?

Try the program again and press:

y

Nothing happens.

Our comparison is case-sensitive:

BSNEQ $wait s0 "Y"

and:

"y"

is not the same string as:

"Y"

We've already solved this kind of problem.

As you've seen in NCL 202, we can normalize text before comparing it.

Add:

SUPR s0 s0

after reading the key:

D.TXT "HIT Y TO CONTINUE"
D.BLT

$wait
SYS.AKEY s0
SUPR s0 s0
BSNEQ $wait s0 "Y"

D.TXT "\r\nCONTINUING..."
D.BLT

Now either:

Y

or:

y

becomes:

"Y"

before the comparison.

The keyboard gave us a string.

The same string operations we've already learned work on it normally.

Confirm? (Y/N)

Waiting for one particular key is useful.

Now let's allow two meaningful choices:

CONFIRM? (Y/N)

We want:

  • Y to confirm;
  • N to cancel;
  • anything else to keep waiting.

Start with the prompt:

D.TXT "CONFIRM? (Y/N) "
D.BLT

Then wait for a key and normalize it:

$input
SYS.AKEY s0
SUPR s0 s0

Now we can choose between the possible values.

If the key is Y, jump to $yes:

BSEQ $yes s0 "Y"

If it isn't N either, go back and wait for another key:

BSNEQ $input s0 "N"

If execution continues past that comparison, the key must have been N.

So the cancellation path can simply fall through:

D.TXT "\r\nCANCELLED"
D.BLT
JUMP 0

Then add the confirmation path:

$yes
D.TXT "\r\nCONFIRMED"
D.BLT

The complete program is:

D.TXT "CONFIRM? (Y/N) "
D.BLT

$input
SYS.AKEY s0
SUPR s0 s0

BSEQ $yes s0 "Y"
BSNEQ $input s0 "N"

D.TXT "\r\nCANCELLED"
D.BLT
JUMP 0

$yes
D.TXT "\r\nCONFIRMED"
D.BLT

Run it and try several keys.

Press:

Q

The program waits again.

Press:

3

It waits again.

Press:

n

SUPR turns it into "N".

The BSEQ for "Y" fails.

The BSNEQ for "N" also fails.

Execution falls through to:

CANCELLED

Run it again and press:

y

This time execution takes the $yes branch and displays:

CONFIRMED

We've built this kind of multi-path choice before.

The important difference is where the value came from.

Earlier, our programs usually made decisions using values already stored or calculated by the program.

Now the person using the computer can decide which path the program takes.

SYS.KEY or SYS.AKEY?

Both operations read the oldest key waiting in the keyboard queue.

The question is what your program should do when no key is available.

Use:

SYS.KEY

when checking the keyboard is only one thing your program wants to do, and continuing without a key is acceptable.

Use:

SYS.AKEY

when the program cannot continue usefully until a key arrives.

In this lesson, our prompts require an answer before anything else should happen, so SYS.AKEY is the natural choice.

Later, an interactive program may have other work to do while no key is waiting. In that situation, SYS.KEY can be useful because it lets execution continue immediately.

Try it

Change the confirmation program so that it asks:

RETRY? (R/Q)

Use:

R

for:

RETRYING

and:

Q

for:

QUITTING

Other keys should be ignored.

You can keep the same overall arrangement:

  1. wait for a key;
  2. normalize it;
  3. compare it with the accepted choices;
  4. choose a path.

You only need to change the values and messages.


A single key is enough for confirmations, menu choices, controls, and simple commands.

But what if we want the person using the computer to type something like:

EMMA

The keyboard still gives us input one key at a time.

In NCL 302: Building User Input, we'll use the string operations from the 200-level to collect those individual keys into a complete piece of text.