So far you can read a field, and replace what's in it. Often you want to do something more specific:
add to a field without replacing it, empty it, count what's in it, or pick out one part of it. That's what
field operators are for. You've already met one: .clear, in
name.clear.
A field operator goes after a dot, just like a field name. They all start with a dot when you write them after a name, so this guide writes them that way too: .clear, .push, and so on.
.clear empties a field:
<message:box {Some text}>
<message.clear> <! the box is now empty !>
Without a field name, as here, it clears the primary field. Clearing a box's primary field removes everything inside it, including any tag instances.
.clear empties a tag instance but leaves it in place. .delete removes the tag instance itself, along with its name:
<message:box {Some text}>
<message.delete> <! the box is gone, and so is the name "message" !>
There's also a clear tag that clears whatever it's inside. At the top level of your
output, <clear> clears the whole console, ready to draw a fresh screen.
.push adds to the end of a field, without removing what's already there:
<message:box {Hello}>
<message.push {, world}> <! the box now says "Hello, world" !>
<message.push <button {OK}>> <! and now there's a button after the text !>
That last line shows one of the most useful things about .push: it lets you add new tag instances to part of the screen you've already created.
This program builds a list as the user adds items. Everything happens inside the console: the program itself only waits for the user to quit.
print('''
<span bold=true size=16,pt {Shopping list}></p>
<item:input>
<button {Add} onClick=<list.push <span {<item></n>}>>, item.clear>
<button {Clear all} onClick=<list.clear>>
<button {Quit} onClick=<putLn {/quit}>></p>
<list:box width=250 border=1 margin=0.5,em {}>
''')
while input() != '/quit':
pass
Shopping listEach click of Add pushes a new span into the list box. The span contains whatever is in item at that moment, followed by a line break.
The buttons refer to list before it's been created. That's fine: their code only runs when they're clicked, and by then list exists.
If the user types something like <b> or { into the box, it appears in the list exactly as typed. Values inside the console are never read as markup: only your program's output is. Problems with special characters only happen when your program prints the user's text, as you saw in the last chapter.
There's a second, more subtle difference between .push and replacing a field's value.
When you replace a value (<name.field new value>), any tags in the new value
are carried out first, and their results are what get stored. When you push, the tags themselves
are moved into the field, and carried out there.
Most of the time the result is the same. But it matters a great deal for code fields. Suppose you want to give a button something to do after it's been created:
<go:button onClick=null {Go}>
<go.onClick <putLn {clicked}>> <! wrong: sends "clicked" straight away !>
<go.onClick.push <putLn {clicked}>> <! right: sends "clicked" each time it's clicked !>
The first line carries out the putLn immediately, while trying to work out the new value, so the line is sent straight away and nothing useful is stored. The second line moves the putLn tag into the code field, where it waits to be carried out on each click.
Rule of thumb: to add code to a code field, or new tags to something already on screen, always use .push.
If you open a .push and don't close it straight away, everything your program prints goes into that field until you close it. This is called a push environment. It's handy when your program produces a lot of output over time that belongs in one place, like a log:
import time
print('''
<span bold=true {Installer}></n>
<log:box width=300 height=120 border=1 {}></n>
<status:span {Starting...}>
''')
print('<log.push {', end='') # open a push environment: output now goes into the log box
for step in ['Checking disk space', 'Copying files', 'Updating settings']:
print(step + '...</n>')
time.sleep(1)
print('}>') # close it: output goes back to the console
print('<status {Done!}></n>')
print('Thanks for waiting.')
input()
InstallerThe lines appear in the log box one at a time, as the program prints them. A <clear>
printed inside a push environment clears the box it's pushing into.
The rest of the field operators are most useful with lists. A vector tag holds a list of values, much like a list in Python. Like a scalar, it's invisible:
<colours:vector {red}, {green}, {blue}>
Each value in the list is called an element. You can add elements with .push:
<colours.push {yellow}, {purple}> <! now 5 elements !>
.# generates the number of elements in a list:
<putLn colours.#> <! sends 5 !>
.@ picks out elements by their position. Positions start at 1, and negative positions count back from the end:
| Code | Generates |
|---|---|
<colours.@ 1> | the first element, red |
<colours.@ -1> | the last element, purple |
<colours.@ 2,4> | elements 2 to 4: green, blue, yellow |
<colours.@ 1,-1,2> | every second element from first to last: red, blue, purple |
Asking for a position that doesn't exist gives invalid, and an "index out of range" error in the terminal.
.? works the other way round: give it a value, and it generates the positions where that value appears. If it doesn't appear at all, nothing is generated:
<pets:vector {cat}, {dog}, {cat}, {fish}>
<where:vector <pets.? {cat}>> <! where holds the positions 1 and 3 !>
Operators that take values, like .@ and .?, need angle brackets of their own, as in<pets.? {cat}>. Without them, there's no way to tell which values belong to the operator: in<putLn pets.? {cat}>, the {cat} is taken as a second value for putLn, not as what to search for.
.pull generates everything in a field, and empties the field at the same time. It's like reading the field and then clearing it, in one step. It's useful for things like a queue of messages that should each be dealt with once.
The same operators work on text too. On a scalar field holding a piece of text, such as an input box's contents or a scalar tag, .#, .@ and .? work on the characters of the text instead of on elements:
<name:input {Grace Hopper}>
name.# <! 12: the number of characters !>
<name.@ 1> <! "G": the first character !>
<name.@ -6,-1> <! "Hopper": the last six characters !>
<name.? { }> <! 6: the position of the space !>
This is handy for checking what the user has typed before sending it to your program, all inside the console. For example, this button only sends the name if it's at least two characters long:
<button {OK} onClick=<if <ge name.#, 2> then=<putLn name> else=<hint {Please type your name.}>>>
<hint:span {}>
(if and ge, which make the decision here, are explained in the chapter on logic.)
One last operator, which you'll rarely need. Some fields react when they're given a new value, such as an image displaying a new picture. If you build up a value bit by bit with several .pushes, the field can't tell when you've finished. .commit tells it the value is now complete. You'll see it used in the chapter on images and media.
| Operator | What it does |
|---|---|
| .clear | empties the field |
| .push | adds to the end of the field; tags are carried out in the field, not before |
| .pull | generates the field's contents, then empties it |
| .# | generates how many elements (or characters) the field holds |
| .@ | generates the element(s), or character(s) of text, at the given positions |
| .? | generates the positions where a value (or a piece of text) appears |
| .delete | removes the tag instance itself |
| .commit | tells the field a value built up with pushes is complete |