Our RPN calculator's programming language is very limited, but we can easily extend it to support enough features to allow serious programming. Consider, for example, the problems of adding control structures to this calculator's language:
To make a programmable calculator truly interesting, it should have the ability to execute programs that contain loops! The simplest way to support this would be to allow conditional gotos of some kind, but it is more fun to support some kind of structured notion of iteration. Consider the following:
E (DP E1+)
How can these iteration commands be implemented in an interpreter for our calculator? There are two ways to do this, and each illustrates useful ideas.
Consider, as a basic framework, the following bit of Pascal code, an abridged version of the code given when the calculator example was originally presented:
procedure calculate(var line: string); var i: 0..stringsize; begin i := 0; while s[i] <> endofstring do begin case s[i] of 'E':begin sp := sp + 1; stack[sp] := 0; end; ... { other commands } '(': { code for begin loop } ')': { code for end loop } end; i := i + 1; end; end;To handle loops in the interpreter, we will simply need to move the input string pointer (the variable i above) back to the start of the loop whenever the end of the loop is encountered. One way to do this is to search back to the start of the loop:
'(': { ignore begin loop } ')': { code for end loop } repeat i := i - 1; until s[i] = '{';This code will work only with loops that are not nested. To handle nesting, we need an auxiliary integer variable, call it level, which is used to skip over inner loops. This leads to the following code:
')': { code for end loop } begin level := 1; repeat i := i - 1; if s[i] = '{' then level := level - 1 else if s[i] = '}' then level := level + 1; until level = 0; end;This is ugly, but it will correctly skip over any number of nested loops to get to the start of the correct loop.
The above approach uses a search for the start of the loop, but it is clearly more elegant to use something analogous to a direct branch. This can be done quite easily by remembering the location of the loop start when the start of the loop is encountered, as illustrated by the following:
'(': { remember begin loop } looploc := i; ')': { code for end loop } i := looploc;Again, this code will work only with loops that are not nested. To handle nesting, we need to replace the above with code that uses a stack, for example:
'(': { remember begin loop } push_on_loop_stack( i ); ')': { code for end loop } i := pop_from_loop_stack;The stack used for pushing and popping loop start and end locations must not be the same stack that is used for pushing and popping the calculator's operands!
Loops without a loop exit mechanism are of only limited interest, so consider adding the following commands to the calculator:
(E1=) -- an infinite loop (1 = 0 is always false) (E1>) -- an infinite loop (1 > 0 is always false) (E1<) -- never iterates (1 < 0 is always true).
v = 5; for (;;) { printf( "%d ", v ); if (v < 1) break; v = v - 1; }Translating this to our calculator language gives:
E5 ( DP D< E1- )Spaces have been used to separate this into 6 blocks of calculator instructions, each of which corresponds exactly to one of the lines of the above C code!
How can we implement these loop exit commands in an interpretive implementation of our calculator? Consider using a simple search for the end of loop whenever a loop exit fails, and merging this with the first implementation of loops given above:
'(': { ignore begin loop } ')': { code for end loop } repeat i := i - 1; until s[i] = '('; '=': { code for compare on equal } begin if stack[sp] = 0 then begin { search for end loop } repeat i := i + 1; until s[i] = ')'; end; sp := sp - 1; end;This code works only for non-nested loops! Adding support for nesting is trivial using the ideas discussed earlier and is left as an exercise.
But! This approach to loops is not efficient! It would be far better to scan the source line once, before trying to calculate what is going on, and build an auxiliary line that indicates, for each "branch" instruction, where on the source line that branch is to go. Consider the following:
_______________________________________________________________ | | | | | | | | | | | | | | | | | Source: | E | 5 | | ( | | D | P | | D | < | | E | 1 | - | | ) | |___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___| 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 _______________________________________________________________ | | | | | | | | | | | | | | | | | Branch: | | | | | | | | | | 16| | | | | | 3 | |___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|___|Here, the first scan of the source line produced the branch targets for the end-loop and the exit on less-than commands and stored these in the branch array. Then, while the calculator is actually calculating, all it needs to do when it finds that the command in Source[i] requires a branch is to set i to Branch[i].