NCL 203: Looking Inside Strings
In NCL 202, we learned how to change strings and combine them to create new ones.
Sometimes, though, we don't need to change a string.
We need to learn something about it.
For example, suppose we have:
#message "Hello!"
How many characters does that string contain?
Finding the length
SLEN finds the length of a string:
#length r0
#message "Hello!"
SLEN #length #message
D.TXT #length
D.BLT
The program displays:
6
#message is a string, but its length is a number.
SLEN examines the string and stores its result in an integer register:
"Hello!" ── SLEN ──> 6
string integer
This is the first time we've used a string operation to produce information that our existing integer instructions can work with.
Using the length
Once we have the length in an integer register, we can use it just like any other integer.
For example:
#length r0
#message "Hello!"
#limit 10
SLEN #length #message
BGT $long #length #limit
D.TXT "Short"
JUMP $done
$long
D.TXT "Long"
$done
D.BLT
The program displays:
Short
SLEN stores 6 in #length.
Then:
BGT $long #length #limit
asks whether that length is greater than 10.
It isn't, so execution continues downward and displays "Short".
Try changing:
#message "Hello!"
to:
#message "This is a considerably longer message."
Before running the program, predict which path it will take.
The program isn't making a decision about a number we wrote directly into an instruction. It is making a decision using information it discovered about a string.
The pattern should look familiar:
- Examine a value.
- Store the result.
- Compare the result.
- Choose a path.
We've done the same thing with calculations such as MOD. Now we're applying that pattern to text.
The empty string
A string does not have to contain any characters at all.
This:
""
is an empty string.
The two quotation marks mark the beginning and end of the string, but there is nothing between them.
Its length is therefore zero:
#length r0
#message ""
SLEN #length #message
D.TXT #length
D.BLT
The program displays:
0
That gives us a simple way to check whether a string is empty:
#length r0
#message ""
SLEN #length #message
BEQ $empty #length 0
D.TXT "Not empty"
JUMP $done
$empty
D.TXT "Empty"
$done
D.BLT
Change #message to different strings and predict which path the program will take.
Finding text inside a string
Length tells us how many characters a string contains.
Sometimes we want to know where some text appears inside it.
For that, NCL provides SFIND.
#position r0
#message "Hello, world!"
#search "world"
SFIND #position #message #search
D.TXT #position
D.BLT
The program displays:
7
SFIND searches #message for the string in #search.
If it finds a match, it stores the position where that match begins in #position.
String positions
Positions within a string begin at 0.
For:
Hello, world!
the positions look like this:
Hello, world!
0123456789012
The first character, H, is at position 0.
The w in "world" is at position 7.
So:
SFIND #position #message #search
stores:
7
in #position.
When nothing is found
What happens if the string we're looking for isn't there?
Try:
#position r0
#message "Hello, world!"
#search "goodbye"
SFIND #position #message #search
D.TXT #position
D.BLT
The program displays:
-1
SFIND returns -1 when it cannot find the requested string.
That gives us a useful distinction:
SFIND result |
Meaning |
|---|---|
0 or greater |
Found, beginning at that position |
-1 |
Not found |
We can use that result to choose a path.
Does a string contain something?
For example:
#position r0
#message "Hello, world!"
#search "world"
SFIND #position #message #search
BEQ $missing #position -1
D.TXT "Found"
JUMP $done
$missing
D.TXT "Not found"
$done
D.BLT
SFIND finds "world" at position 7.
Since 7 is not -1, the branch is not taken and the program displays:
Found
Change:
#search "world"
to:
#search "goodbye"
Now SFIND returns -1, the branch is taken, and the program displays:
Not found
This lets the program answer a useful question:
Does this string contain this other string anywhere?
But SFIND gives us more information than simply yes or no.
It tells us where the match occurred.
Does a string start with something?
Consider:
#position r0
#message "NCS/e READY"
#search "NCS/e"
SFIND #position #message #search
SFIND returns 0.
Since position 0 is the beginning of a string, we now know that #message starts with "NCS/e".
We can test for that directly:
#position r0
#message "NCS/e READY"
#search "NCS/e"
SFIND #position #message #search
BEQ $starts #position 0
D.TXT "Does not start with "
D.TXT #search
JUMP $done
$starts
D.TXT "Starts with "
D.TXT #search
$done
D.BLT
The program displays:
Starts with NCS/e
Now change the message:
#message "READY: NCS/e"
SFIND still finds "NCS/e", but this time it finds it at position 7.
The string contains "NCS/e", but does not start with it.
And if we use:
#message "READY"
SFIND returns -1 because "NCS/e" does not appear at all.
| Message | SFIND result |
Contains NCS/e? |
Starts with NCS/e? |
|---|---|---|---|
NCS/e READY |
0 | Yes | Yes |
READY: NCS/e |
7 | Yes | No |
READY |
-1 | No | No |
The same SFIND result can therefore answer different questions depending on how we use it.
Combining what we know
We can combine SLEN, SFIND, and the control flow we learned in the 100-level lessons to classify a string in several ways:
#length r0
#position r1
#message "Hello from NCS/e"
#search "NCS"
SLEN #length #message
BEQ $empty #length 0
SFIND #position #message #search
BEQ $starts #position 0
BEQ $missing #position -1
D.TXT "Contains "
D.TXT #search
JUMP $done
$starts
D.TXT "Starts with "
D.TXT #search
JUMP $done
$missing
D.TXT "No match"
JUMP $done
$empty
D.TXT "Empty"
$done
D.BLT
Follow the program from the beginning.
First:
SLEN #length #message
BEQ $empty #length 0
checks whether the message is empty.
It isn't, so the program continues.
Then:
SFIND #position #message #search
searches for "NCS".
For the current message, the result is greater than 0.
That means:
BEQ $starts #position 0
is not taken, and:
BEQ $missing #position -1
is not taken either.
Execution continues downward and displays:
Contains NCS
Try changing #message to:
#message "NCS/e READY"
then:
#message "Hello!"
and finally:
#message ""
Before running each version, follow the branches and predict which message will be displayed.
Try it: ignoring capitalization
SFIND distinguishes between uppercase and lowercase letters.
Consider:
#position r0
#message s0
#search s1
SMOVE #message "Hello from NCS/e"
SMOVE #search "ncs/E"
SFIND #position #message #search
D.TXT #position
D.BLT
The capitalization of "NCS/e" and "ncs/E" does not match, so the search fails.
But we already know how to change the case of a string.
Modify the program so that the search works regardless of how the letters in #message and #search are capitalized.
You can use either SUPR or SLWR.
Remember that both strings need to be converted to the same case before SFIND compares them.
Once your program works, try changing the capitalization of either original string:
SMOVE #message "HELLO FROM ncs/E"
SMOVE #search "NcS/e"
The search should still succeed.
For an extra challenge, modify the program so that the strings stored in #message and #search remain unchanged.
You'll need two more string registers to hold the converted versions.
By combining operations we've already learned, we've made a case-insensitive search without needing a new search instruction.
SFIND can tell us where part of a string begins.
In NCL 204: Taking Strings Apart, we'll use positions like these to start extracting pieces from strings.