#rakulang blang: implementing if statement, and assignments


Continuing on from my previous post, I thought I’d like to tackle the IF statement. The general syntax is IF expr THEN ... FI. I won’t tackle ELSE here.

Recall that my goal is to embed VM instructions in the rakulang grammar itself, rather than to have an actions class that walks through the resultant syntax tree. Based on my experiments so far, this is a viable approach, and I think it will change the way I approach other language-parsing attempts.

So, how are we going to implement “IF”? My solution is to borrow from the way that a Forth IF is usually implemented: you “immediately” push the current HERE location onto the stack, add a conditional jump statement that points to nowhere in particular, tack on the instructions until you find the matching endif, then backpatch the new HERE location to the old HERE location stored on the stacks.

To achieve all this, I introduce a new BCODE (binary code) instruction JZE, which performs a relative jump if the top of the stack is zero. You therefore jump to the end of the IF if the expression evaluates to the equivalent of false.

So our Bcodes now look like this:

enum Bcode <Add Call Div Drop Dup Halt Inc Jlt Jze Mul Push Sub>;

I introduce a new auxiliary function, “here”, which points to the end of the bcode vector:

sub here() { return elems(@bcodes); }

It is equivalent of the Forth HERE instruction, for those that are familiar with Forth.

I’ll use a separate stack to hold the locations of the IFs:

my @ifs; # locations of if statements

I could use the normal data stack if I wanted. That’s the way Forth does it.

In my grammar, I add <if-stm> to my statement rule to handle the IF statement. The IF statement is implemented as follows:

rule if-stm     { 'if' <expr> 'then' { mk-if; } <stmts>  'fi' {mk-fi;} }

Not so bad, right? We do need to define MK-IF (make an if statement) and MK-FI (make end of if):

sub mk-if() {
         @ifs.push(here);
         bpush0 Jze;
 }
 sub mk-fi() {
         my $loc = @ifs.pop;
         @bvals[$loc] = here-$loc;
 }

That’s pretty much it.

Let’s test it out using the file IF.BAS

print "should print 1"
 if 2 then
     print 1
     if 3 then 
         print "nothing further should be printed"
     fi
 fi
 if 0 then
     print "this should never be printed"
 fi

which outputs:

0    Push    0
 1    Call    printkstr
 2    Push    2
 3    Jze 7
 4    Push    1
 5    Call    print
 6    Push    3
 7    Jze 3
 8    Push    1
 9    Call    printkstr
 10    Push    0
 11    Jze 3
 12    Push    2
 13    Call    printkstr
 14    Halt    0
 should print 1
 1
 nothing further should be printed
 Bye

Super.

The interpreter is hardly complete, but we’ve accomplished a lot with just a little over 200 lines of Raku code.

The code can be found on my github account.

Thanks for reading. Stay safe.

Update 2020-11-03: assignments

I decided to add variable assignments, too. So we add a new assignment grammar rule:

rule assign     {  <id> '='  { mk-assign $<id>.Str; }  }

where

sub mk-assign(Str $varname) {
 my $i = findex(@num-var-names, $varname);
 if $i < 0 {
 $i = elems(@num-var-names);
 @num-var-names.push($varname);
 @num-var-values.push(0);
 }
 bpush Ass, $i;
} 

and

sub findex(@keys, $key) {
 loop (my $i=0; $i <elems(@keys); $i++)  {
 if $key eq @keys[$i] { return $i; }
 }
 return -1;
} 

We define a couple of arrays: one to hold the variable names, and another to hold their values:

my Str @num-var-names;
my @num-var-values;

I can’t use hash tables because the bcodes (bytecodes) take numbers, not strings. There’s also an added advantage: if I want to compile AOT (Ahead Of Time) then I can serialise everything to a file and deserialise it later. So things like strings and variables can be stored via array indices. No addresses.

I made the design decision to keep the names of the variables and their values separate, rather than combining them in pairs. I don’t think I lose anything doing it this way.

We also need to enhance our expression primitive grammar rule to include variable lookups:

rule expr-prim  { ( <int> { bpush Push, $.Int; }) | ( <id> {mk-get-varn $<id>.Str;}) }

where

sub mk-get-varn(Str $varname) {
 my $i = findex(@num-var-names, $varname);
 if $i < 0 {
 $i = elems(@num-var-names);
 @num-var-names.push($varname);
 @num-var-values.push(0);
 }
 bpush Getn, $i;
} 

Finally, we need two handlers in our VM execution:

when Ass { @num-var-values[$val] = spop; }
when Getn { spush @num-var-values[$val]; }

Let’s test it gout: assign.bas:

#test of assignment
 foo = 12 + 13
 bar = 3-2
 print "Expect 26:"
 print foo + bar

The output is as expected.

Cool. The line count is creeping up: now at 243 lines. We’re still not done, of course, but the functionality is coming along nicely.

I have been musing about string variables. Should I allow strings to have the same names as numbers, or should I use a sigil (dollar symbol at end of variable name, as is traditional for Basic)? I’ve come done towards the latter, as arithmetic operations are quite different from string ones.

I hope you found that interesting.

About mcturra2000

Computer programmer living in Scotland.
This entry was posted in Uncategorized. Bookmark the permalink.

1 Response to #rakulang blang: implementing if statement, and assignments

  1. Pingback: 2020.45 Cro Serviced – Rakudo Weekly News

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s