Labels & Subroutines

Implementing labels

So far our language is just a list of commands - the computer is yet to think for itself, or make decisions based on data we give it. That is to change in the next couple of parts. First of all, we'll give the interpreter the ability to jump around the source code we give it at will.

A label is a marker placed in source code. We can jump to this marker from a certain point in code. For example:

MSG Hello!
JUMP testlabel
MSG World!
LBL testlabel

Would see the command "MSG World!" skipped completely as we jump to the label just below it.

First of all, we need a place to keep a list of all our labels. Add the following to your form declarations:

' Label lookup
Dim Labels As New Collection

We can now write two new functions, on to add a label to our list of labels, and one to get the position of a label:

Function CreateLabel(LabelName As String, LabelPos As Integer)
Labels.Add LabelPos, LabelName
End Function

Function FindLabel(LabelName As String) As Integer
FindLabel = Labels(LabelName)
End Function

Pretty harmless, eh? ;) If you remember in the previous part we had to empty the list of variables each time we ran the program, otherwise we would get "variable already exists" errors. Add a call to EmptyCollection in Init, this time for the label list:

'Initialises
Sub Init()

' Clean var list
EmptyCollection Vars

' Clean label list
EmptyCollection Labels

End Sub

Finally, we can add two commands. A command to declare a label, and a command to jump to a label. Here are the case statements, to go in the usual place:

        ' Declare a label
        Case "lbl"
            CreateLabel Token(), LineNum
            
        ' Jump to a label
        Case "jump"
            LineNum = FindLabel(Token())

Note that at the moment we can ONLY jump to labels we've declared in a previous line of code. Not very good, and means the only way we can test the program is by putting it in a never-ending loop. So SAVE RIGHT NOW then try this. You'll need to Ctrl-Alt-Del to close VB, so I suggest you save!

LBL begin
MSG Hello
JUMP begin

To fix this problem, we need to go through the code beforehand, finding all the labels, and declaring them then. So, to complement the sub MainPass, create a new one called LabelPass. Here's my code for it:

' Pass through code for labels
Sub LabelPass()

Dim LN As Integer
Dim CodeLine As String

LN = 0

' Loop thru lines looking for a label + declare
For Each CodeLine In Code
    Tokenise (CodeLine)
    
    If LCase(Token()) = "lbl" Then
        CreateLabel Token(), LN
    End If

    LN = LN + 1

Next CodeLine

End Sub

Note that this sub also needs acces to the array Code() which is created in MainPass. Hence, we can move the line

' Load code into array in 1-line chunks
Code = Split(txtCode.Text, vbCrLf)

Out of MainPass, and into Parse() like so. Note I've also added the call to LabelPass in here:

'Parses a complete program
Sub Parse()

' Load code into array in 1-line chunks
Code = Split(txtCode.Text, vbCrLf)

Init
LabelPass
MainPass

End Sub

Now try the following code:

jump end
msg hello
lbl end

You should end up with... nothing. If nothing happens, well done! If you get any errors don't worry - writing this section was complex and so a few errors might have crept into my code. However I can guarantee the downloadable project files work, so just grab the code out of there.

Subroutines

A subroutine is a section of code that can be called, and then at a specific point you return to exactly where you left off. As a few examples - look at the VB subs we've been using here! A section of code for example, I called Init, is called in Parse(), then at "End Sub" we return to where we left off.

It's easy to jump to a certain point in code - just use a label. But the returning? We need to have a way of saving our last line number, then skipping back to that when we hit a "return" command. That's easy, what is hard is coping with things like this:

SUB MySub1
GOSUB MySub2
RETURN

SUB MySub2
GOSUB MySub3
RETURN

SUB MySub3
GOSUB MySub4
RETURN

SUB MySub4
GOSUB MySub5
RETURN

And so on. Confused yet? That sort of nesting happens a lot - and would be a devil to manage.

I give you, the stack.

Stacks

Imagine you have a few books, and you pile themn up. First you put a red book down, then a blue book down, and lastly you put a green book on top of the pile. You now have a pile of 3 books. A stack of books even.

Now, take the first one off the top. That the last one you put on. The take another one off, that's the blue one - the penultimate one you put on. Your final book off is the first one you put on. This is what a stack is - a first in, last out list.

In computing terms you can do two things with a stack - you can put data onto it (called a "push") or you can take some data off the top (called a "pop"). I'll be using a stack to keep a list of return line numbers for nested subroutines.

Here's how it will work:

Here's how it would work in real life:

00: GOSUB MySub1
01: END.
02: SUB MySub1
03: GOSUB MySub2
04: RETURN
05:
06: SUB MySub2
07: MSG Hello
08: RETURN

Note that when jumping to a line number the current line number is immediately incremented again after the case statement - so when jumping to line 6, the next line to be executed is line 7 etc.

Since VB6 does not have a stack object built in, I use this class I found on the internet (credit to whoever's it is!)

Private colData As Collection

Private Sub Class_Initialize()
    Set colData = New Collection
End Sub

Public Sub Clear()
    Set colData = New Collection
End Sub

Public Function Count() As Long
    Count = colData.Count
End Function

Public Sub Push(value As Variant)
    colData.Add value
End Sub

Public Function Pop() As Variant
    Pop = colData.Item(colData.Count)
    colData.Remove colData.Count
End Function

Public Function Peek() As Variant
    Peek = colData.Item(colData.Count)
End Function

Private Sub Class_Terminate()
    Set colData = Nothing
End Sub

Copy this into a new class called Stack and save (I called the file "Stack.cls"). Now add this declaration at the top of the form:

' For nested subs
Dim ReturnTargets As New Stack

And finally the two case statements for dealing with "gosub" and "return" commands.

        ' Go to a label as a subroutine
        Case "gosub"
            ReturnLine = LineNum + 1
        
            ' Add to the stack of return destinations
            LabelPos = FindLabel(Token())
            ReturnTargets.Push ReturnLine
            
            ' Go to line
            LineNum = LabelPos
            
        ' Return from a sub
        Case "return"
            ReturnLine = Val(ReturnTargets.Pop)
            LineNum = ReturnLine - 1

And that's it. Implementing subroutines in hardly any time at all. Once we have the basic label system working, lots of doors open for us. In the next part I will show you how to have Do, Loop, Repeat, Until, While and If commands. I'll also deal with boolean comparisions (if value = value), and how to make decisions based on whether a comparison was true or false.

Download project files

The project files can be downloaded here