Memory & Variables
Memory addresses
For our language, there will be 26 memory addresses, one for each letter of the alphabet. These act like temporary variables, giving space to store and process data before storing it permanently in a variable.
First things first, let's declare an array to hold all of the data at the beginning of the code:
' Info for mem addresses Dim MemAddresses(256) As Variant
After doing so, we need to to define two functions to ease the process of using the memory spaces. First, I will create a function to store a value into memory:
Sub MoveIntoMem(address As String, value As Variant) MemAddresses(Asc(address)) = value End Sub
You will see that in the declarations section we define the memory array as having 256 slots - that is, one for each ASCII code. For pure ease of use we will simply convert the character of the address given into ASCII code, then store a value into that array slot (and so it is possible to not only reference to memory by A...Z but also any numbers, special characters etc.).
Next, we need a function to pull a value out of a memory slot when we pass it an address. It is simply the reverse of the above:
Function GetFromMem(address As String) GetFromMem = MemAddresses(Asc(address)) End Function
With these two functions we can now define a new command for our language - one to store a number into a memory address. I will call this command MOVE. An example of the syntax is below:
Move number 1 into slot 'a' MOVE 1 a Move number 2 into slot 'b' MOVE 2 b
The code for this should hopefully be straightforward to work out - so here is the new Case statement for it. As always, put inside MainPass. I am doing something slightly out of the ordinary here- I've placed all the code for the command inside a separate sub - this is only to aid readability.
' Move command
Case "move"
CMove
And the sub CMove:
Sub CMove()
Dim value As Integer
Dim addr As String
' Get the value and make it a number
value = Val(Token())
' Get the address name
addr = Token()
' Store it
MoveIntoMem addr, value
End Sub
As an example of how to get data out of an address, here's the code for a command that echoes the value of an address:
' Echo address
Case "addrout"
MsgBox Str(GetFromMem(Token()))
Test your new version by entering the following code:
MOVE 1 A MOVE 2 B MOVE 999 Z ADDROUT A ADDROUT B ADDROUT Z END.
You should hopefully see 3 message boxes, each with the number 1, then 2, then 999.
Challenge: Try to write a command that will copy the value of one address to another. This isn't essential to the language, but may come in useful.
Variables
Almost every language has variables, and ours will be no different. In our small language however, they'll be intended for long term storage. My main aim for this language was to create code similar in style to assembly but without all the oddities, variations and annoyances. Hence most temporary actions, such as addition, comparision etc. will take place between memory addresses. Variables are there to store things in the long run with a more descriptive name.
Our variable data is going to be store in what's called a collection. A collection is similar to an array except it provides us with the ability to use textual keys - so instead of
Array(1) = something
we can do
Array("name") = something
This is ideal for variables, as each variable has its own unique name. First things first, let's add a new set of declarations:
' Variable lookup Dim Vars As New CollectionNow we have the collection lets, as is usual, define a few functions to deal with variables. Firstly, the function DeclareVar:
Function DeclareVar(Name As String, TheValue As Variant)
' If we encounter an error show message
On Error GoTo DeclareVar_Err
Vars.Add TheValue, Name
Exit Function
DeclareVar_Err:
Err ("variable '" + Name + "' already exists")
End Function
You'll see I've incorporated a bit of error handling here - here's the Err() functions that is called to raise an error and halt execution.
Function Err(message As String) MsgBox "Error: " + message + vbCrLf + "Line:" + str(LineNum) LineNum = UBound(Code) : 'Halts execution after this loop End Function
Next are the functions to store a value in a variable, and get a value from a variable:
Function GetVar(Name As String) As Variant On Error GoTo GetVar_Err GetVar = Vars.Item(Name) Exit Function GetVar_Err: Err "Could not retrieve variable value for '" + Name + "', the variable may not be initialised." End Function
And finally the function to store a value in a variable. Since collections don't support you modifying an item, you need to delete it and create it again:
Function LetVar(VName As String, value As Variant) Vars.Remove VName Vars.Add value, VName End Function
Variable commands
Let's now add some commands to the language that utilise these 3 functions:
To declare variable: VAR VariableName [Optional Intial Value] To save value of a memory address to a variable: LET VariableName MemoryAddress To load the value of a variable into an address: LOAD VariableName MemoryAddress
With the syntax sorted, it's very easy to create the code for all 3:
' Declare variable
Case "var"
DeclareVar Token(), Val(Token())
' Let command
Case "let"
LetVar Token(), GetFromMem(Token())
' Load command
Case "load"
Dim VarName As String
Dim TargetAddr As String
VarName = Token()
TargetAddr = Token()
MoveIntoMem TargetAddr, GetVar(VarName)
Note I load the tokens into variables first before calling MoveIntoMem in Load - because the order which the user types the parameters differs from the order needed for MoveIntoMem.
And here is some code you can run through the interpreter to see if its working:
VAR david 1 LOAD david a ADDROUT a (outputs 1) MOVE 99 b LET david b LOAD david c ADDROUT c (outputs 99)
Wait! Try running the above code once, then run it again. You'll get an error! We didn't clear the variable list so it's keeping the information from the previous time. To solve this, we intruduce another sub, Init, which 'primes the pump' and makes sure everything's clean:
'Initialises Sub Init() ' Clean var list EmptyCollection Vars End SubAt this point you'll also need the sub EmptyCollection:
Public Sub EmptyCollection(c As Collection) While c.Count c.Remove 1 Wend End Sub
And finally, you can modify the sub Parse by calling Init before MainPass:
'Parses a complete program Sub Parse() Init MainPass End Sub(looks quite elegant, don't you think? ;-))
That's it for the moment. You can now store data in temporary memory before storing it on variables. I'll leave you with a few command to perform basic operations on memory which will be needed in future (you'll need to add "Dim TName as String" at the beginning of MainPass):
' Add two addresses
Case "add"
val1 = GetFromMem(Token())
TName = Token()
val2 = GetFromMem(TName)
ans = val1 + val2
MoveIntoMem TName, ans
' Subtract two addresses
Case "sub"
val1 = GetFromMem(Token())
TName = Token()
val2 = GetFromMem(TName)
ans = val1 - val2
MoveIntoMem TName, ans
' Negate
Case "neg"
TName = Token()
val1 = GetFromMem(TName)
val1 = val1 * -1
MoveIntoMem TName, val1
' Multiply two addresses (3 PARAMS - TWO SOURCE ADDRESSES, AND AN ADDRESS TO SAVE RESULT)
Case "mul"
val1 = GetFromMem(Token())
val2 = GetFromMem(Token())
ans = val1 * val2
MoveIntoMem Token(), ans
' Divide two addresses (3 PARAMS AS ABOVE)
Case "div"
val1 = GetFromMem(Token())
val2 = GetFromMem(Token())
ans = val1 / val2
MoveIntoMem Token(), ans