Group Name: T04_G02
Student Numbers and Names:
- up202108823: João Pedro Oliveira Sequeira
- up202008790: Tiago Rocha Silveira Pires
- João Pedro Oliveira Sequeira - 50%
- Tiago Rocha Silveira Pires - 50%
- We decided to use a modular approach by separating the data structures, Interpreter, and compiler into distinct modules (
container.hs,Interpreter.hs,parser.hs). - The
Containermodule defines the instructions, stack, and state types. - The
Interpretermodule contains functions to interpret instructions, manipulate the stack and state, and run the code. - The
Testsmodule provides a set of tests for the Interpreter. - The
Parsermodule is currently a placeholder for future development.
The Container module provides a simple implementation of a stack-based virtual machine. It defines data structures for code, stack, and state, along with functions to operate on them.
The Inst data type represents the instructions that can be executed in the virtual machine. Instructions include operations like arithmetic, logic, and control flow. The Code type is a list of instructions, forming a program.
Push Integer: Push an integer onto the stack.Add,Mult,Sub,Tru,Fals,Equ,Le,And,Neg: Arithmetic and logical operations.Fetch String: Load the value of a variable from the state onto the stack.Store String: Store a value from the stack into a variable in the state.Noop: No-operation.Branch Code Code: Conditional branch.Loop Code Code: Loop construct.
The Stack data type is a stack of StackValues, where a StackValue can be either a number, True (TT), or False (FF).
pushToStack: Push a value onto the stack.pop: Pop a value from the stack.createEmptyStack: Create an empty stack.stack2Str: Convert a stack to a string.
The State data type is a key-value store implemented using Data.Map.Strict. Keys are strings, and values are StackValues.
insertIntoState: Insert a key-value pair into the state.createEmptyState: Create an empty state.state2Str: Convert a state to a string.
popCode: Pop an instruction from the code.
The Interpreter module implements an interpreter for a simple stack-based programming language. It utilizes the data structures and functions provided by the Container module.
This function performs addition. It pops two values from the stack, adds them, and pushes the result back onto the stack. If there are not enough values on the stack or if the values are not numbers, it throws a run-time error.
Similar to add, this function performs multiplication.
Handles subtraction by popping two values, subtracting them, and pushing the result back onto the stack.
Implements equality comparison. It handles cases where the values on the stack can be numbers or boolean values (True or False). It pushes True (TT) onto the stack if the values are equal; otherwise, it pushes False (FF).
Performs less than or equal to comparison for numbers on the stack. It pushes True (TT) if the first value is less than or equal to the second; otherwise, it pushes False (FF).
Implements logical AND. It pushes True (TT) onto the stack if both values on the stack are True; otherwise, it pushes False (FF).
Implements logical negation. It flips the boolean value on the top of the stack.
Pushes a value onto the stack. It takes an Either Integer Bool parameter, where Left n represents an integer value, and Right True or Right False represents boolean values.
Loads the value of a variable from the state onto the stack. It takes a key, searches for it in the state, and pushes the corresponding value onto the stack.
Stores a value from the stack into a variable in the state. It pops a value from the stack, and if the key exists in the state, it updates the state with the new value.
Conditional branch based on the top of the stack. It takes two code flows and the current stack. Depending on whether the top of the stack is True (TT) or False (FF), it selects the corresponding code flow and updates the stack.
Implements a loop construct. It takes two code flows and creates a loop structure using the Branch and Noop instructions.
A dummy function that receives the stack and state and returns them unchanged. It serves as a no-operation placeholder.
The main function for executing code. It takes a triple (Code, Stack, State) and iteratively processes instructions until the code is empty. It updates the stack and state based on the executed instructions.
In summary, the Interpreter module provides a set of functions to perform arithmetic and logic operations, manipulate the stack, handle control flow, and execute a sequence of instructions. It builds on the data structures and functions defined in the Container module, creating a flexible and extensible interpreter for a stack-based language.
The Tests module includes tests for the Assembler, Interpreter, and Parser modules. It defines a set of functions to test the functionality of the stack-based programming language, from assembly to parsing.
This function takes a code and returns a tuple containing the string representations of the stack and state after running the code. It uses the run function from the Interpreter module to execute the code.
This IO action runs a series of tests using testAssembler and prints the results. The tests include various code scenarios to ensure the correct functioning of the assembler.
-
The
Parsermodule is currently incomplete in a way that it doesnt meet the criteria for the parser tests on the while and if statements. -
This was do to problems we had building the functions regarding those statements and some debug issues we had on which we couldnt unfortunetely complete this functions with success. The tests that dont pass are commented on tests.hs
-
Regarding the parser.hs file we started by defining data and types that we thought would be used on this second stage of the project
data Aexp = ADD Aexp Aexp
| SUB Aexp Aexp
| MULT Aexp Aexp
| Num Integer
| Var String
deriving (Eq, Show)
-- verified
data Bexp = TRUE
| FALSE
| NOT Bexp
| AND Bexp Bexp
| LEQ Aexp Aexp
| Eq Aexp Aexp -- expressoes com =
| EqBool Bexp Bexp -- expressoes com ==
deriving (Eq, Show)
-- verified
data Stm = Assign String Aexp
| Seq Stm Stm
| IfThenElse Bexp [Stm] [Stm]
| While Bexp [Stm]
deriving (Eq, Show)
-- verified
type Program = [Stm]We then proceded to implement the compiler that would be responsible for receiving an argument and return the Code (list with program instructions). We created compilers for both arithmetic expressions and boolean expressions, 'compA' and 'compB' respectively
compile :: Program -> Code
compile [] = []
compile (stmt:rest) = case stmt of
Assign var expr -> compA expr ++ [Store var] ++ compile rest
Seq stmt1 stmt2 -> compile [stmt1] ++ compile [stmt2] ++ compile rest
IfThenElse cond thenStmt elseStmt ->
compB cond ++ [Branch (compile thenStmt) (compile elseStmt)] ++ compile rest
While cond body ->
[Branch (compB cond ++ [Branch (compile body ++ [Branch (compile [While cond body]) [Noop]]) [Noop]]) [Noop]] ++ compile rest
This is what the main compile function looks like! It processes each statement in the program based on its type. For the assign case we compute the expression value and store it in the corresponding variable; for sequence statements we went to recursion to compile each sub-statement and concatenate the result; On the If Else case we generate code to evaluate the condition and branch accordingly; For the while statements we do a loop structure using conditional branches and no operation instructions.
Regarding the parser itself we tried to follow the requirements of the project on its definition. We used lexer as instructed on the theoretical classes to help us separate the content of the instructions and store it on a list of strings. We also implemented several parsing expressions based on the theoretical slides that helped us parse the different arithmetic and boolean expressions.
To help the parser function we created a parsing function to aid passing the string list to lexer. Once it receives the list it will iterate throught it while trying to match each case of the parsing regarding parenthesis, if and else statements, while statements and assignment statements. (If and else statements are not working as expected).
Follow these steps to run the project:
-
Download the Project:
- Download or clone the project repository to your local machine.
-
Navigate to the
srcFolder:- Open a terminal or command prompt.
- Navigate to the
srcfolder of the project using thecdcommand.
-
Run GHCi (Glasgow Haskell Compiler Interactive):
- Type the following command to start GHCi:
ghci
- Type the following command to start GHCi:
-
Load the Main Module:
- Once GHCi is running, load the main module using the following command:
:l main.hs
- Once GHCi is running, load the main module using the following command: