I'm writing a simple stack-based language in C and was wondering how I should go about implementing a loop structure of some kind, and/or lookahead symbols. Since the code is a bit long for this page (over 200 lines) I've put it in a GitHub repository.
EDIT: The main program is in file stack.c
.
EDIT: The code just takes in input in words
, kind of like FORTH. It uses scanf
and works left to right. Then it uses a series of if
s and strcmp
s to decide what to do. That's really it.
Your language isn't at all Forth-like, so don't copy Forth's (compilation-only - a meaningless distinction for your language) loop structures.
Looking at your code, add
until
as a conditional restart-evaluation word. That is, add it as a normal word alongside yourrange
andjump
, have it pop the stack, and if the top of the stack was true, set stack.c'scmd
back to the beginning., in your language, will produce the output
0 1 2 3 4 5 6
, across three evaluation, and re-evaluating the second line several times. This assumes that you preserve state across evaluations, and that evaluation is line-oriented. You might mine LSE64 for more ideas like this.The Forth approach is to add a separate loop stack alongside the data stack. You then define operations that work with this loop stack. For example:
Will print
The way this works is:
DO
moves the index (0) and the control (5) over to the loop stack.I
copies the top of the loop stack to the data stack.LOOP
increments the index (top of loop stack). If the index is less than the control (one below the top of loop stack), then it reruns the commands fromDO
back toLOOP
. If the index is >=, then it pops the index and control from the loop stack, and control resumes as normal.The obvious way to add control structures to a stack-based language is to add a "control stack". I'll describe the Postscript mechanism because that's what I know, but Forth has some analogous behavior (with subtle differences, to be sure).
The simplest controlled loop is the
repeat
loop. Technically, an infiniteloop
is simpler, but to use it requires an explicitexit
command, and explaining that would complicate things.The syntax for repeat is:
So when the
repeat
command begins it expects to find a procedure on the top of the operand stack (this is the loop body to be executed), and an integer just below the procedure (the number of times to execute the procedure).Since there is also an execution stack (I think Forth calls it the "return" stack), a command can "execute" other commands by pushing them on the execution stack, thus scheduling other commands to be executed immediately after the current command is finished, but before resuming the caller of the current command. That's a long sentence, but the key idea is there.
As a concrete example, assume the interpreter is executing from the input stream. And the stacks look like this:
The user types
2 {(Hello World!\n) print} repeat
.The integer 2 gets pushed on the stack.
The quoted(*) procedure body gets pushed on the stack.
The
repeat
command gets executed.Executing the repeat procedure (once) leaves the stacks like this:
The interpreter executes the procedure on top of the exec stack, printing "Hello World!", then finds an integer, which it pushes on the op stack.
The interpreter executes a quoted procedure by pushing on the op stack.
And we're back at the beginning! Ready for the next iteration (or termination if the integer has fallen to zero).
Hope this helps.
Edit;
After actually looking at your code, I have a different suggestion, perhaps a springboard toward something like I describe above. I think you should forget the code for a while and focus on data structures. You've got a nice table for variables, but all the commands are embedded literals scattered through the code!
If you make your table hold variable record types, you can use the same lookup mechanism for both (and even move up to a hash or a ternary search tree (my current favorite)). I have in mind something like this:
This way each command goes in its own function. The bonus is: small functions. You should try to make your functions small enough that you can see the whole thing on the screen at the same time. Scanning the whole thing at once allows your right brain to do some of the work of detecting problem areas.
Then you'll need another type to hold sequences of commands. An array or list should work. When you can define sequences, you can repeat sequences much more easily.
The bonus here is you're only one construct away from Turing Complete. Sequences, Iterations, Decisions (or Selection): that's all you need to program any computable function!
*. As discovered by the commentator, Postscript does not actually have the same concept of quoting that I use here in my description. Here I'm borrowing the concept from Lisp terminology. Postscript has instead a literal/executable flag that can be set
cvx
cvlit
or testedxcheck
. An executable array on the execution stack will be executed. So for the "quoted" procedure-body, it's actually a literal procedure-body (ie. an array). Because of this,repeat
must also push a call tocvx
to be executed before the next iteration. So, a more correct pseudocode for the postscript implementation ofrepeat
is:This performs the necessary flag-twiddling to pass the procedure as data on the execution stack.
Another way that I've seen this control structure implemented is with two functions,
repeat
the same entry-point, and an internal continuation operator which in theory doesn't need a name. I think ghostscript calls it something like @repeat-continue@. With a separate continue function, you don't have to be quite so careful with the order of stuff on the stack, and you don't need to twiddle the literal flag. Instead, you can store some persistent data below the recursive call on the exec stack; but you have to clean it up too.So an alternate
repeat
would be:The the continuation also has a simpler job.
Finally, the
exit
operator is intimately connected to loops, it clears the exec stack up to the "frame" of the looping operator. For the 2-function style, this is anull
object or similar. For the 1-function style, this is the looping operator itself.Here's a blog post in which DO/LOOP, BEGIN/UNTIL, WHILE/REPEAT, etc. are implimented in my little TransForth project: http://blogs.msdn.com/b/ashleyf/archive/2011/02/06/loopty-do-i-loop.aspx
I've since changed my mind however and rely entirely on recursion with no such looping words.
Hope that helps, have fun!