Creating an Interpreter for Monty Bytecodes Using C Programming Language

Robert Amoah
6 min readJun 17, 2023

--

PART THREE

This is a link to the previous part (PART TWO) of the series: https://medium.com/@mr_robertamoah/creating-an-interpreter-for-monty-bytecodes-using-c-programming-language-d5757cb08358

A recap of what we have done so far. We have to create a program which basically checks the number of arguments; opens a file and reads its content line after line while tracking the line number; for each line read, we break a copy of the line into words/tokens; check if the first word in the line is a valid Monty bytecode opcode; and then call the appropriate function associated with the opcode.

For now, all the functions associated with the opcodes, examples being push, pall, nop, add, etc, are all empty. Our task for this part and the next ones will be to implement these functions. Let’s start with the implementation of the push and pall functions. The push opcode should have an integer argument and its associating function requires that we create a stack_s node which has the n member being the opcode’s integer argument. For example, if we read the line “push 5”, then tokens member of arguments will have the following tokens and indexes:

table 0

Let’s look at the push function, and then I will explain what I did there, further.

pushing an instruction_s node on the stack
push.c
validating integers
is_number.c

I first check if the number of tokens is greater than 1 or if the second token, which is 5, in the example above, is not a valid integer. In that case, push cannot be implemented and hence I free up all allocated memory using the free_arguments function and then print the required string in that situation and exit the program. I will leave the is_number function to you to figure out. It just checks if the string is a valid number. Once this check fails, we have a valid push opcode, so I allocate memory to the stack we passed into the function. Note that it’s a pointer to pointer so it has to be properly dereferenced. Using atoi standard library function, I set the n member of the stack node to the argument of push opcode, which is 5 in our example. I then check if the head of the stack is not NULL (meaning there is node in the linked list). In that case I insert the node to the beginning of the linked list. After, I make the head member of the arguments global variable to point to the new stack node. Also, I keep track of the length of the linked list using the stack_length member of arguments. Clearly, you see that there are new members of the arg_s struct. Yes! I leave that to you to do in your header file. The new members are head and stack_length. After updating the arg_s struct in your header file, you would have to initialize these members in the initialize_function as well. Again, that is a task for you. I will inspect them. Well I think your compiler will do a better job at punishing you if you do not complete this task 😂😂.

There is something I should have done better in the push function in the case allocation of memory to the *stack failed. Do you know what it is? Tell me in the comment section for a lollipop 😊.

For the pall opcode, it is to print the n member value of all the nodes of our linked list, starting from the first node in the link. How would I implement that? If you know your linked list, the first thing that should come to mind is iteration, right? Have a look.

printing nodes in the linked list
pall.c

I just checked if there were no nodes in the linked list. In that case there will be nothing to print so I return from the function. Otherwise, I loop through the linked list and print the n member followed by a newline.

Have you thought of the data structure we are using at the moment? Is it a stack or queue? Think carefully about it for a minute. We insert nodes to the beginning of the linked list. And when printing, we print from the beginning as well. At the time of printing, the first to be printed is actually the last node added right? Can we say it’s a last in first out kind of situation? You see what I am getting at? Yes, I think you get it. It’s a stack data structure at the moment. So, by default, we are implementing our Monty opcodes’ interpreter using stack data structure. We will try to implement the interpreter using queue data structure later on.

Now compile code and let’s run the program with the bytecode below:

bytecode 0

According to the bytecode above, we are to push stack_s node with the following n values: 1, 2, and 3, in that order, to the stack. Before running the pall, node with n of 3 will be at the top hence pall should print 3, 2 and 1. Let us run the program and see if it works.

standard output

This works as we expected. One thing you shouldn’t forget is that we still haven’t implemented our free_arguments function. We can do that now.

free the member variables of the arguments variable as well as the arguments variable itself
free_arguments.c
free the linked list starting from the head
free_head.c
a recursive approach to freeing the nodes in a linked list
free_stack.c

In the free_arguments function, I have freed the instruction, head, and line members as well as the arguments variable itself. The free_head function is used to free the linked list which is pointed to by the head member of the arguments pointer. It also, in turn, calls the free_stack function which uses a recursive approach to free up memory allocated for all the nodes in the linked list.

The tokens and stream member variables have been handled outside of the free_arguments function with free_tokens and close_stream functions. We have seen these earlier. For this reason, I decided to create another function that calls all three functions in the proper order, which will be used in the case where there is an error in any of the opcode implementation functions. I call this function free_all_args. This is how that also looks like:

closing stream, freeing tokens and free all other arguments related variables
free_all_args.c

For the free_all_args function, the stream (FILE *) is closed; the tokens array is freed; then all other allocations made to the arguments global variable are freed.

Having implemented these, we will look at the implementation of the pint opcode. This will be the last for this part of the series of articles. The pint opcode prints the value at the top (last node to be added) of the stack, followed by a new line. Let’s look at how I implemented it.

print the head node
pint.c

For the free_all_args function, the stream (FILE *) is closed; the tokens array is freed; then all other allocations made to the arguments global variable are freed.

Having implemented these, we will look at the implementation of the pint opcode. This will be the last for this part of the series of articles. The pint opcode prints the value at the top (last node to be added) of the stack, followed by a new line. Let’s look at how I implemented it.

This is the link to the next part (PART FOUR) of the series of articles: https://medium.com/@mr_robertamoah/creating-an-interpreter-for-monty-bytecodes-using-c-programming-language-1bd363ed924f

--

--

Robert Amoah

Full-stack web developer #laravel #nestjs #vue #react Mobile Application Development #reactnative DevOps and Cloud enthusiast #linux #bash Love Data Science