The TRSE programming language is called “Rascal”.
Rascal is heavily inspired by Pascal and Algol, with some C sprinkled in for convenience.
Some of the quirks of TRSE are from necessity – there is a limit to what you can implement in a language or compiler and still successfully target the 1980s computers based around 6502 and Z80 processors!
In this article we will look more deeply at the Rascal programming syntax, and build on our earlier example TRSE program that runs on the Commodore 64/128, Vic 20 and Commodore PET.
Previously we looked at an example “hello world” TRSE program:
program Example; var b : byte=0; // 0-255 i : integer=0; // 0-65535 p : pointer; // address in memory s : string; // a string array of characters hello_string: cstring = ("HELLO WORLD!"); begin // Blank the screen with spaces ClearScreen(key_space, #screen_char_loc); // Set the entire screen text color to white ClearScreen(white, screen_col_loc); // Move the cursor to 10,10 moveto(10,10, hi(screen_char_loc)); // Print hello world then loop forever printstring(#hello_string,0,12); Loop(); end.
As you can see, there are various variable types that you might expect:
- Byte: Unsigned number from 0-255
- Integer: Number between 0 and 65,535, stored as two bytes, “little endian” (smaller byte first).
- Pointer: A 16-bit address to a location in memory. TRSE pointers are stored in the Zero Page area of memory. You can not set the value at declaration time.
- String: A string of text characters, terminated by 0
- CString: A string of PETSCII text characters, optionally terminated by 0
All variables in Rascal are global in scope, it doesn’t matter if they are declared in a procedure, in a unit or at the start of your program.
You can declare a string with a value both with a literal enclosed in ” and also using the character code:
mystr: cstring = ("STRING", 34, 45, 99, "TEXT");
It is also possible to include raw data at a memory location at compile time:
Finally, you can have constants:
Variables at Set Memory Locations
const MYCONST : byte = 127;
Rascal Arrays currently only support bytes and integers and indexes are bytes, so you can store a maximum of 255 values (zero-based). If you want to have your array reference something else, use your array to store pointers (for example pointers to strings).
You can not resize an array, they are fixed at declaration.
my_array : array of byte;
Reference the contents of the array using the index in square brackets:
my_array[ x ] := 127;
Due to the low-level way TRSE works, you can reference the screen memory as if it was an array by utilizing pointers:
screenmemory := 1024; screenmemory[ x ] := 32;
TRSE Built-In Constants
Turbo Racal defines a whole bunch of constants for you, and the values will change between platforms. The intention is, of course, to abstract some of the details so that your code can run on different machines.
We have already met screen_char_loc and screen_col_loc which you can use to reference the screen and color memory locations. Set the Target System in Project Settings to PET or Vic 20 and back to Commodore 64 and you will see the value change:
This will come in handy as we explore coding for multiple systems!
Closely related to the built-in constants is the concept of pointers and the related symbols that you will start seeing pop up:
# address of
$ hex value
The difference between a pointer and what the pointer points to can be confusing and is an important part of developing in Rascal due to how often you will need to manipulate the contents of memory.
// Display the value of SCREEN_CHAR_LOC printDecimal(#SCREEN_CHAR_LOC,6); // Display what SCREEN_CHAR_LOC points to printDecimal(SCREEN_CHAR_LOC,6);
Running this code will output 1024 and 32, respectively.
#SCREEN_CHAR_LOC is set to the address of the C64 screen memory, which is 1024 in decimal, whereas without the # we are asking for whatever is at the location that we are pointing at. Memory location 1024 contains a blank space, which is character 32 in decimal.
If Then Else
Your program will have to make decisions, and this is where IF comes in. While your IF can be all on one line if it is a straightforward “if this then”, the full structure looks like this:
if (door = locked) then begin locked(); end else begin not_locked(); end;
Note: only the final end has a ; to terminate the statement!
Of course, as in many languages, you can test for two conditions with
if (door = locked and shutter = down) then
You can use the following comparison operators in your IF statement:
|>=||Greater or equal to|
|<=||Less or equal to|
|<>||Not equal to|
Case statements save you from a lot of complex multiple-if situations:
case x of 1: printstring("1",0,1); 2: begin printstring("2",0,1); do_something_else(); end; 3: printstring("3",0,1); else begin // None of the above printstring("?",0,1); end;
(The Else part is optional)
While works similar to if statements with the same comparison operators:
while x < 40 do begin screenmemory[x] := 127; x := x + 1; end;
For loops will iterate until they reach the target value, but the index is a byte so by default you can only loop up to 256 iterations.
for x := 0 to 39 do begin screenmemory[x] := 127; end;
The minimal procedure in Rascal looks like this:
procedure hello_world( my_param: integer ); var x : byte; begin PrintString("Hello!", 6); end;
In this example we would be able to pass along an integer value called “my_param”, though you do not need to declare any parameter variables to pass. Note the procedure ends with a ;
If you want your procedure to be called before it is defined then you can use a forward reference and define it later in your code:
procedure Procedure2(); // A forward reference has no begin or end statement
While all variables in Rascal are global, it is a good practice to pass parameters back and forth to your subroutines to keep things readable and to prevent having to remember what every variable is called or is for.
If you want to return a value then use a Function and specify the return value type:
function my_function( val: integer ) : integer; begin // Set the return value of the function my_function := val + 10; end;
Earlier we output “Hello World” and we discussed outputting the the value of the screen memory location.
Here is the full code for that below.
As you can see, we are mostly using in-built methods!
We clear the screen with spaces, and set the text color – same function, different parameters. This is because behind the scenes it is really just copying values to memory locations, first the screen memory and then the color memory.
To set the location on screen that we want to write to we use MoveTo, and pass in the high byte of the screen memory address.
Our output is using PrintString and PrintDecimal, and that allows us to put text and numbers on screen.
What do we get when we run this on Vic 20 and Commodore PET?
program Screen_Test; var hello_string: cstring = ("HELLO WORLD!"); begin // Initialise screen values DefineScreen(); // Blank the screen with spaces ClearScreen(key_space, #SCREEN_CHAR_LOC); // Set the entire screen text color to white ClearScreen(blue, SCREEN_COL_LOC); // Move the cursor to 10,10 moveto(10,10, hi(SCREEN_CHAR_LOC)); // Print hello world printstring(#hello_string,0,12); // Move the cursor to 10,20 moveto(10,20, hi(SCREEN_CHAR_LOC)); // Display the value of SCREEN_CHAR_LOC printDecimal(#SCREEN_CHAR_LOC,6); // Move the cursor to 10,22 moveto(10,22, hi(SCREEN_CHAR_LOC)); // Display what SCREEN_CHAR_LOC points to printDecimal(SCREEN_CHAR_LOC,6); Loop(); end.
Now one thing to note about line 15, the Commodore PET has no concept of screen colors as the pixels are either on or off on that machine. On Vic 20 the background color is already white so setting the text color to white wouldn’t make sense.
How would you change our program so the code works as expected?