INPUT /  OUTPUT /  LANGUAGE





Reference Pointers

Suppose you have several boxes on the screen, and the user can pick one of them. Later, you want to change whichever box they picked. How do you write code that acts on "the chosen box", when you don't know in advance which one it will be?

Using the box's name doesn't help. Writing <chosen:scalar a> copies the contents of box a's primary field into chosen. It doesn't remember the box itself. What you need is a value that points to a tag instance: a reference pointer.


Getting a reference: the .& operator

Add .& to the end of a name to get a reference to that tag instance, rather than its contents:

<a:box {Hello}>
<chosen:scalar a.&>     <! chosen now points to box a !>

A reference is a value like any other. You can store it in a scalar or a vector, and replace it with a different one later. (If you give a name to a .& expression directly, as in <chosen:a.&>, the reference is automatically stored in a scalar.)

A reference is only an address, though. Print it, and all you see is ref.

Following a reference: the .* operator

To use the tag instance a reference points to, add .* after the name of the scalar holding the reference. Think of the reference as an address written on a piece of paper: .* means "go to that address". After .*, you can do anything you could do with the tag instance's own name:

<a:box width=100 {Hello}>
<chosen:scalar a.&>

<putLn chosen.*>            <! sends "Hello": a's primary field !>
<putLn chosen.*.width>      <! sends 100: one of a's fields !>
<chosen.*.width 200>        <! changes a's width !>
<chosen.* {Goodbye}>        <! changes what's in box a !>
<chosen.*.push <span {!}>> <! adds a new tag instance inside box a !>

Only a scalar can be dereferenced. A vector might hold several references or none, so there'd be no way to know which one to follow. To follow a reference stored in a vector, copy it into a scalar first (there's an example below).

References to fields

.& works on fields too. A reference to a field lets you change that field without knowing which tag it belongs to:

<a:box width=100 {Hello}>
<size:scalar a.width.&>
<size.* 250>                <! sets a.width to 250 !>

Example: picking a box

Back to the problem we started with. In this program, clicking a box highlights it and removes the highlight from whichever box was highlighted before. The selected scalar always points to the current choice. Each box's onClick code clears the old highlight through the reference, points the reference at itself, and then highlights itself through the reference.

print('''
<selected:scalar null>

<a:box width=100 height=60 border=1
    onClick=
        <if selected then=<selected.*.backgroundColor null>,<selected.*.color null>>,
        <selected a.&>,
        <selected.*.backgroundColor {gold}>,
        <selected.*.color {black}>
    {A}
>
<b:box width=100 height=60 border=1
    onClick=
        <if selected then=<selected.*.backgroundColor null>,<selected.*.color null>>,
        <selected b.&>,
        <selected.*.backgroundColor {gold}>,
        <selected.*.color {black}>
    {B}
>
''')
input()

The two onClick fields are identical except for the name after <selected. The code that does the highlighting never mentions a or b; it only goes through the reference. (In the chapter on defining your own tags, you'll see how to avoid writing that code twice.)

Neither box is created with a backgroundColor or color field. Changing a field through a reference works just like changing it by name: if the box doesn't have the field yet, it gains it the first time it's used.

Example: keeping track of new tag instances

References are especially useful for tag instances your program creates as it goes along. This program adds notes to a board, and stores a reference to each new note in a vector. Later it can get any note back by its position in the list, without needing to know its name:

notes = ['Buy milk', 'Walk the dog', 'Write essay']

print('<board:box {}>')
print('<refs:vector>')          # the list of references, empty for now
print('<current:scalar null>')  # used to follow one reference at a time

# create a note for each item, and remember a reference to it
for n, text in enumerate(notes, start=1):
    print(f'<board.push <note{n}:box border=1 {{{text}}}>>')
    print(f'<refs.push board.note{n}.&>')

# later: mark note 2 as done
print('<current <refs.@ 2>>')         # copy the 2nd reference into a scalar...
print('<current.*.backgroundColor {pink}>')   # ...so it can be followed
print('<current.*.color {black}>')
print('<current.* {Walk the dog (done)}>')
input()

Two things to notice:

(The braces in {{{text}}} are tripled because this is a Python f-string: {{ and }} print a literal brace, and {text} inserts the note's text.)

References to array elements

As you saw in the previous chapter, an array creates many tag instances from one definition. Its first and last fields are references to its first and last elements, and .*.& gets a reference to the current element. That reference keeps pointing to the same element even after the array moves on to a different one:

<i:array size=3 *=<box {element <i>}>>
<i 2>                       <! make element 2 the current element !>
<kept:scalar i.*.&>         <! remember element 2 !>
<i 3>                       <! move on to element 3 !>
<putLn kept.*>              <! still sends "element 2" !>

When a reference goes stale

A reference points to one particular tag instance, not to a name. If that tag instance is removed (for example, because the box containing it was cleared), the reference has nothing left to point to. Following it then gives invalid, and an error appears in the terminal.

You can spot a stale reference by printing it: instead of ref, it shows as ~ref~. Creating a new tag instance with the same name as the old one doesn't bring the reference back to life. The new tag instance is a different one.

References stay inside the console

A reference only means something inside the ioL console. If you send one to your program with putLn, the program just receives the text ref. Keep references in scalars and vectors inside the console, and send your program names or numbers instead.


Try it

  1. Add a third box, c, to the "picking a box" example.
  2. Add a button to the "picking a box" example that makes the currently selected box twice as wide. It should work whichever box is selected.
  3. In the notes example, mark every note as done, one at a time, by copying each reference out of the vector in turn.
  4. Clear the board after storing the references, then print one of the references. What do you see? What happens if you try to follow it?