Hone logo
Hone
Problems

Building a Simple Executor in Rust

This challenge asks you to implement a basic executor for a simple instruction set in Rust. Executors are fundamental to programming language implementations, responsible for fetching, decoding, and executing instructions. This exercise will give you a deeper understanding of how interpreters and virtual machines work.

Problem Description

You are tasked with creating a simple executor for a hypothetical instruction set. The instruction set consists of the following instructions:

  • PUSH <value>: Pushes the given value (an integer) onto the stack.
  • POP: Pops the top value from the stack.
  • ADD: Pops the top two values from the stack, adds them, and pushes the result back onto the stack.
  • SUB: Pops the top two values from the stack, subtracts the top value from the second value, and pushes the result back onto the stack.
  • MUL: Pops the top two values from the stack, multiplies them, and pushes the result back onto the stack.
  • DIV: Pops the top two values from the stack, divides the second value by the top value (integer division), and pushes the result back onto the stack.
  • HALT: Stops the execution.

The executor should take a vector of instructions (represented as strings) as input and execute them sequentially. The executor should maintain a stack to store and manipulate values. The executor should return the final state of the stack after execution. If an error occurs (e.g., division by zero, insufficient values on the stack), the executor should return an empty stack.

Key Requirements:

  • Implement the execute function that takes a vector of instruction strings and returns a Vec<i32> representing the final stack state.
  • Handle invalid instructions gracefully by returning an empty stack.
  • Handle division by zero by returning an empty stack.
  • Handle cases where there are not enough values on the stack for an operation (POP, ADD, SUB, MUL, DIV) by returning an empty stack.
  • The stack should be implemented as a Vec<i32>.

Expected Behavior:

The executor should correctly interpret and execute each instruction, updating the stack accordingly. The HALT instruction should terminate the execution.

Examples

Example 1:

Input: ["PUSH 5", "PUSH 3", "ADD", "PUSH 2", "MUL"]
Output: [16]
Explanation:
1. PUSH 5: Stack = [5]
2. PUSH 3: Stack = [5, 3]
3. ADD: Stack = [8]
4. PUSH 2: Stack = [8, 2]
5. MUL: Stack = [16]

Example 2:

Input: ["PUSH 10", "PUSH 2", "DIV"]
Output: [5]
Explanation:
1. PUSH 10: Stack = [10]
2. PUSH 2: Stack = [10, 2]
3. DIV: Stack = [5]

Example 3:

Input: ["PUSH 10", "PUSH 2", "DIV", "PUSH 3", "SUB"]
Output: []
Explanation:
1. PUSH 10: Stack = [10]
2. PUSH 2: Stack = [10, 2]
3. DIV: Stack = [5]
4. PUSH 3: Stack = [5, 3]
5. SUB: Stack = [2]

Example 4: (Edge Case - Division by Zero)

Input: ["PUSH 5", "PUSH 0", "DIV"]
Output: []
Explanation: Division by zero is an error, so the stack is emptied.

Example 5: (Edge Case - Insufficient Values)

Input: ["ADD"]
Output: []
Explanation: ADD requires two values on the stack, but there are none.

Constraints

  • The input vector of instructions will contain only strings.
  • Integer values in PUSH instructions will be within the range of i32.
  • The number of instructions will be less than or equal to 100.
  • The stack size will be limited to 100 elements.
  • Performance is not a primary concern for this exercise; focus on correctness and clarity.

Notes

  • Consider using a match statement to handle the different instructions.
  • Error handling is crucial. Ensure your executor gracefully handles invalid instructions and potential runtime errors.
  • Think about how to parse the instruction strings to extract the instruction and any associated values.
  • Start with a simple set of instructions and gradually add more complexity.
  • Test your executor thoroughly with various inputs, including edge cases.
  • The instructions are space separated. For example, "PUSH 5" is a valid instruction.
Loading editor...
rust