NCL 206: Building and Formatting Strings
In NCL 205, we finished with a small command interpreter.
Part of that program checked that the : separating the command and argument was not at either end of the input:
SFIND #separator #input ":"
BEQ $invalid #separator -1
BEQ $invalid #separator 0
SLEN #length #input
MOVE #last #length
DEC #last
BEQ $invalid #separator #last
Those checks made sure a string like:
SAY:Hello!
contained something on both sides of the separator.
But suppose we decide that extra separators at the ends should simply be ignored.
Instead of rejecting them, we can remove them first.
Trimming strings
STRIML removes a string repeatedly from the left side of another string.
For example:
#message s0
SMOVE #message ":::Hello"
STRIML #message #message ":"
D.TXT #message
D.BLT
The program displays:
Hello
STRIML keeps removing ":" while the beginning of the string matches it.
Similarly, STRIMR removes matches from the right:
#message s0
SMOVE #message "Hello:::"
STRIMR #message #message ":"
D.TXT #message
D.BLT
which also produces:
Hello
Using both:
#message s0
SMOVE #message ":::Hello:::"
STRIML #message #message ":"
STRIMR #message #message ":"
D.TXT #message
D.BLT
produces:
Hello
The string being trimmed does not have to be one character long.
For example:
STRIML #message ".....@" ".."
STRIML repeatedly removes the complete string ".." from the left:
| Step | Value |
|---|---|
| Start | ".....@" |
Remove ".." |
"...@" |
Remove ".." |
".@" |
One "." remains because the beginning no longer matches the complete trimming string "..".
Simplifying our parser
Let's return to:
COMMAND:argument
If separators at the ends are considered unwanted decoration, we can trim them before looking for the separator:
STRIML #input #input ":"
STRIMR #input #input ":"
SFIND #separator #input ":"
BEQ $invalid #separator -1
After trimming, an input such as:
:::SHOUT:Hello!:::
becomes:
SHOUT:Hello!
Any : that SFIND now discovers cannot be the first or final character.
That means we no longer need these checks:
BEQ $invalid #separator 0
or:
SLEN #length #input
MOVE #last #length
DEC #last
BEQ $invalid #separator #last
We changed the input first so that those conditions cannot occur.
This gives us another useful programming technique:
Sometimes it is simpler to put data into a known form before working with it.
We already did something similar in NCL 205 when we used SUPR to normalize commands before comparing them.
Trimming changes what your program accepts
Trimming is useful only when the removed text is not meaningful.
With this parser, ":" characters at the beginning or end are deliberately discarded.
If those characters could be part of the argument itself, trimming them would change the data instead of merely cleaning it up.
Padding strings
Sometimes we have the opposite problem.
Instead of removing characters, we want to make a string longer.
SPADL pads the left side of a string until it reaches a requested length.
#number s0
SMOVE #number "42"
SPADL #number #number "0" 5
D.TXT #number
D.BLT
The program displays:
00042
The original string has length 2.
SPADL repeatedly adds "0" to the left until the result reaches length 5.
SPADR does the same thing on the right:
#label s0
SMOVE #label "READY"
SPADR #label #label "." 10
D.TXT #label
D.BLT
The result is:
READY.....
Padding is especially useful when several values need to occupy consistent amounts of space.
Aligning values
Suppose we want to display a name and score:
EMMA | 42
We can pad the name on the right:
#name s0
SMOVE #name "EMMA"
SPADR #name #name " " 10
giving:
"EMMA "
and pad the score on the left:
#score r0
#scoreText s1
MOVE #score 42
SMOVE #scoreText #score
SPADL #scoreText #scoreText " " 6
giving:
" 42"
Then:
D.TXT #name
D.TXT " | "
D.TXT #scoreText
D.BLT
displays:
EMMA | 42
Change the values:
SMOVE #name "ALICE"
MOVE #score 7
and the same formatting still works:
ALICE | 7
The amount of padding changes to fit the value.
The width of the completed fields does not.
Padding repeats
The padding itself can be more than one character.
For example:
#result s0
SPADR #result "42" "ab" 5
D.TXT #result
D.BLT
produces:
42abab
This result has length 6, even though we requested length 5.
Padding may exceed the requested length
SPADL and SPADR repeatedly add the entire padding string until the requested length is reached or exceeded.
If the padding contains more than one character, the result may therefore be longer than the requested length.
For example:
ncl
SPADR #result "42" "ab" 5
produces:
text
42abab
When an exact length is required, use SSUB afterward to remove the excess from the opposite side.
When an exact length is required, we can use SSUB afterward to remove the excess from the opposite side.
| Padding | Intermediate value | SSUB |
Final value |
|---|---|---|---|
| Right | "42abab" |
SSUB #result #result 0 4 |
"42aba" |
| Left | "abab42" |
SSUB #result #result -5 -1 |
"bab42" |
For right padding, we preserve the requested number of characters from the left.
For left padding, we preserve them from the right.
The negative positions we learned in NCL 204 are useful whenever we need to preserve the end of a string.
Repeating with padding
Padding doesn't have to surround meaningful text.
An empty string has length 0:
""
So we can pad one to create repeated text.
#line s0
SPADR #line "" "-" 12
D.TXT #line
D.BLT
The program displays:
------------
We asked SPADR to make an empty string at least 12 characters long using "-".
The result is twelve copies of "-".
We can use other padding strings too:
SPADR #line "" ".-" 12
produces:
.-.-.-.-.-.-
Here the padding happens to fit the requested length exactly.
If it didn't, we could use SSUB to clamp the result as we just learned.
There isn't a special instruction here for "repeat this string six times."
We're using the behavior of padding to produce the result we want.
Inserting text
SJOIN lets us put strings together at their ends.
Sometimes we need to place new text inside an existing string.
For that, NCL provides SINS.
Consider:
#message s0
SMOVE #message "Hello!"
SINS #message #message ", world" 5
D.TXT #message
D.BLT
The program displays:
Hello, world!
The original positions are:
Hello!
012345
^
5
Position 5 contains the !.
This instruction:
SINS #message #message ", world" 5
inserts the new text at position 5.
The ! and everything after that position move to the right.
We can also calculate the insertion position.
For example:
#position r0
#message s0
SMOVE #message "NAME:"
SLEN #position #message
SINS #message #message " EMMA" #position
D.TXT #message
D.BLT
#position becomes 5, the length of "NAME:".
Inserting at that position produces:
NAME: EMMA
Once again, a value calculated by one string operation becomes an input to another.
Replacing text
Sometimes we don't want to insert new text alongside the old text.
We want to replace something that is already there.
SREP searches for one string and replaces matching occurrences with another.
For example:
#message s0
SMOVE #message "one two two"
SREP #message #message "two" "three" 1
D.TXT #message
D.BLT
The result is:
one three two
The final operand:
1
tells SREP how many matches to replace and which direction to start from.
A positive count replaces from the beginning of the string.
So:
SREP #message #message "two" "three" 1
replaces the first "two".
Replacing from the other end
A negative count works from the end of the string.
#message s0
SMOVE #message "one two two"
SREP #message #message "two" "three" -1
D.TXT #message
D.BLT
produces:
one two three
This time, the last match is replaced.
The magnitude still controls how many matches may be replaced.
So a count of:
-2
means to replace up to two matches, starting from the end.
Replacing everything
A count of 0 has a special meaning:
SREP #message #message "two" "three" 0
replaces all matches.
For:
one two two
the result is:
one three three
So the replacement count works like this:
| Count | Behavior |
|---|---|
| Positive | Replace up to that many matches from the beginning |
| Negative | Replace up to that many matches from the end |
0 |
Replace every match |
This makes SREP useful for both precise edits and broad transformations.
For example:
#message s0
SMOVE #message "NCS_e_READY"
SREP #message #message "_" " " 0
D.TXT #message
D.BLT
produces:
NCS e READY
Putting the tools together
Let's build a small formatted status field from:
***NCS_e_READY***
First, remove the surrounding * characters:
#message s0
SMOVE #message "***NCS_e_READY***"
STRIML #message #message "*"
STRIMR #message #message "*"
Now:
#message = "NCS_e_READY"
Replace every underscore with a space:
SREP #message #message "_" " " 0
giving:
NCS e READY
Insert a marker at the beginning:
SINS #message #message "> " 0
giving:
> NCS e READY
Finally, pad the field to 20 characters:
SPADR #message #message "." 20
and display it:
D.TXT #message
D.BLT
The complete program is:
#message s0
SMOVE #message "***NCS_e_READY***"
STRIML #message #message "*"
STRIMR #message #message "*"
SREP #message #message "_" " " 0
SINS #message #message "> " 0
SPADR #message #message "." 20
D.TXT #message
D.BLT
The result is:
> NCS e READY.......
We didn't need one instruction capable of producing that complete format.
We changed the string a little at a time:
- Trim unwanted text from the ends.
- Replace text inside the string.
- Insert new text at a chosen position.
- Pad the result to a useful width.
Each operation solves a small problem.
Together, they let us reshape strings into the form a program needs.
Try it
Create a small score field from these values:
#name s0
#score r0
#scoreText s1
SMOVE #name "EMMA"
MOVE #score 42
Make the final display look like:
EMMA......|...42
Use "." instead of spaces so you can clearly see the padding while experimenting.
The name should occupy 10 characters:
EMMA......
and the score should occupy 5:
...42
Then try:
SMOVE #name "ALICE"
MOVE #score 7
and predict the result before running it.
For an extra challenge, build this line:
[ALICE]...|....7
Insert the brackets using SINS rather than writing the string "[ALICE]" directly.
Remember that inserting the first bracket changes the positions of everything that follows.
We've learned how to remove, add, repeat, insert, and replace pieces of strings.
In NCL 207: Special Characters, we'll look at characters that are difficult to write directly into a string, and at characters that can affect how text is displayed.