Code Planting Tutorial Part 3 - GetPoplog/Seed GitHub Wiki

Step 3: Labels and Jumps

In this third part, we take a look at how to write conditional code. The underlying model is rather like BASIC or even assembler, where you plant labels in the code-stream and arrange to jump to those labels conditionally or unconditionally. Note that although this is enough to enable you to write loops, there are some extra looping gadgets that are introduced later on, so look out for those later.

In this tutorial it helps to know that:

  • The built-in function dest takes a linked list and returns both the head and tail.
  • A common idiom for stepping down a list is lvars item = mylist.dest -> mylist;

Modelling if-then-else-endif

Let's look at how we model an if E then S1 else S2 endif expression. We will need to:

  • allocate new labels using sysNEW_LABEL()
  • place them in the code stream with sysLABEL
  • conditionally branch using sysIFNOT(label), which jumps to label if the top of the stack is false
  • and unconditionally branch to a label using sysGOTO(label)

Putting these together, we will have code a bit like this. And to temporarily draw a veil over the representation of expressions, we will just assume that plantCodeFor is a function that generates code for expressions such as E, S1 and S2. I have added pseudo-assembler comments on the right hand side that hopefully clarify what is happening on the left.

lvars else_label = sysNEW_LABEL();
lvars endif_label = sysNEW_LABEL();           
plantCodeFor( E );                            ;;;   E
sysIFNOT( else_label );                       ;;;   IFNOT goto else_label
plantCodeFor( S1 );                           ;;;   S1
sysGOTO( endif_label );                       ;;;   GOTO endif_label
sysLABEL( else_label );                       ;;; else_label:
plantCodeFor( S2 );                           ;;;   S2
sysLABEL( endif_label );                      ;;; endif_label:

It is worth noticing that if this was just an if E then S endif expression then it would be a bit simpler:

lvars endif_label = sysNEW_LABEL();           
plantCodeFor( E );                            ;;;   E
sysIFNOT( endif_label );                      ;;;   IFNOT goto endif_label
plantCodeFor( S );                            ;;;   S1
sysLABEL( endif_label );                      ;;; endif_label:

We can try to capture this by writing a plantIfThenElse function, which suppresses the 'else' part if S2 is false.

define plantIfThenElse( E, S1, S2 ); lvars E, S1, S2;
    lvars endif_label = sysNEW_LABEL();    
    lvars else_label = S2 and sysNEW_LABEL() or endif_label;
    plantCodeFor( E );                            ;;;   E
    sysIFNOT( else_label );                       ;;;   IFNOT goto else_label
    plantCodeFor( S1 );                           ;;;   S1
    if S2 then                                    ;;; 
        sysGOTO( endif_label );                   ;;;   GOTO endif_label
        sysLABEL( else_label );                   ;;; else_label:
        plantCodeFor( S2 );                       ;;;   S2
    endif;                                        ;;; 
    sysLABEL( endif_label );                      ;;; endif_label:
enddefine;

Chained if-then-elseif-then ...

LISP can express a chain of if-then-else's rather neatly using COND, which takes an arbitrary number of if-then pairs and an optional-else part. It chains along the if-then pairs and if none of them are true executes the optional-else (or does nothing). Let's see how we can implement that efficiently.

The basic pattern for (COND E1 S1 E2 S2 ... S_ELSE) looks like this:

  E1
  IFNOT goto E2_label
  S1
  GOTO endif_label
E2_label: 

  E2
  IFNOT goto E3_label
  S2
  GOTO endif_label
E3_label:

  S_ELSE

endif_label:

And let's put this into code. It's a simple enough pattern. There's an idiomatic use of dest that returns both the head and tail of a list to walk the cond_list in pairs, carefully managing the else case.

define plantCondList( cond_list ); lvars cond_list;
    lvars endif_label = sysNEW_LABEL();
    until null( cond_list ) do
        lvars Ei = cond_list.dest -> cond_list;
        if null( cond_list ) then
            ;;; Ei is S_ELSE
            plantCodeFor( Ei );            
        else
            lvars Si = cond_list.dest -> cond_list;
            plantCodeFor( Ei );
            lvars next_label = sysNEW_LABEL();
            sysIFNOT( next_label );
            plantCodeFor( Si );
            sysGOTO( endif_label );
            sysLABEL( next_label );
        endif
    enduntil;
    sysLABEL( endif_label );
enddefine;

Visually checking code-planting

Code-planting code can easily get complicated and difficult to read. It's very common to want to inspect the stream of code that is being emitted. To do that load the library LIB SHOWCODE.

lib showcode

and then modify plantCondList to locally set the variable pop_show_code to true.

define plantCondList( cond_list ); lvars cond_list;
    dlocal pop_show_code = true;
    ... rest of the code as before ...
enddefine;

Now we will get a trace of the code stream. To see this in action we will give the placeholder plantCodeFor function a simple definition. It will just push a constant onto the stack (sysPUSHQ).

define plantCodeFor( E ); lvars E;
    sysPUSHQ( E )
enddefine;

So now we can try a sequence such as [ ^false 1 ^false 2 ^true 3 4 ], which ought to have the same meaning as:

if false then 1 
elseif false then 2
elseif true then 3
else 4
endif

Let's try it out:

: vars procedure f1f2t34 = buildNewProcedure( plantCondList(% [ ^false 1 ^false 2 ^true 3 4 ] %) );
        PUSHQ   <false>
        IFNOT   label_20
        PUSHQ   1
        GOTO    label_19
    label_20:
        PUSHQ   <false>
        IFNOT   label_21
        PUSHQ   2
        GOTO    label_19
    label_21:
        PUSHQ   <true>
        IFNOT   label_22
        PUSHQ   3
        GOTO    label_19
    label_22:
        PUSHQ   4
    label_19:
: f1f2t34() =>
** 3
: 

Nice! Now you may be asking whether or not you can see the code generated by the Pop-11 interpreter too? Sure, we just invoke the Pop-11 compiler on an input stream - in this case drawn from a string.

: vars some_code = 'if false then 1 elseif false then 2 elseif true then 3 else 4 endif';
: procedure(); dlocal pop_show_code = true; some_code.stringin.pop11_compile endprocedure() =>
    COMPILE <procedure pop11_exec_stmnt_seq_to>
        PUSH    false
        IFNOT   label_28
        PUSHQ   1
        GOTO    label_27
    label_28:
        PUSH    false
        IFNOT   label_29
        PUSHQ   2
        GOTO    label_27
    label_29:
        PUSH    true
        IFNOT   label_30
        PUSHQ   3
        GOTO    label_27
    label_30:
        PUSHQ   4
    label_27:
        EXECUTE
** 3
: 

Which, unsurprisingly, looks just like the code emitted by our plantCondList.

Next Step

⚠️ **GitHub.com Fallback** ⚠️