diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 7ac4ba1..253934c 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest] - test_suite: [test_ast, test_st] + test_suite: [test_ast, test_st, test_all] steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/Makefile b/Makefile index 2551e32..0538cc9 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,7 @@ PYTEST = pytest .PHONY: install run tests test test_ast test_st test_all clean all check_python check_pip # Default target -all: install clean test_ast clean +all: install clean test_all clean # Install dependencies install: check_python check_pip requirements.txt @@ -77,7 +77,7 @@ run: install main.py test.txt # Run normal tests test: @echo "Running tests...$(OS)" - @if [ "$(OS)" = "Windows" ] && [ "$(R)" = "" ]; then \ + @if [ "$(OS)" = "Windows_NT" ] && [ "$(R)" = "" ]; then \ if [ "$(F)" = "" ]; then \ $(PYTHON) -m pytest -v --no-summary rpal_tests/test_generate_tests_with_rpal_exe.py ; \ else \ @@ -134,6 +134,9 @@ test_all: echo "=========================================================================================================="; \ echo "Running tests for Standardized Syntax Tree (ST):"; \ $(PYTHON) -m pytest -q rpal_tests/test_generate_st_tests_with_rpal_exe.py ; \ + echo "=========================================================================================================="; \ + echo "Running tests:"; \ + $(PYTHON) -m pytest -q rpal_tests/test_generate_tests_with_rpal_exe.py ; \ else \ echo "=========================================================================================================="; \ echo "Running tests for Abstract Syntax Tree (AST):"; \ @@ -141,6 +144,9 @@ test_all: echo "=========================================================================================================="; \ echo "Running tests for Standardized Syntax Tree (ST):"; \ $(PYTHON) -m pytest -q rpal_tests/test_generate_st_tests_with_rpal_exe.py ; \ + echo "=========================================================================================================="; \ + echo "Running tests :"; \ + $(PYTHON) -m pytest -q rpal_tests/test_generate_tests.py ; \ fi # Clean up generated files diff --git a/README.md b/README.md index f270228..ad551eb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ ![Tests](https://github.com/malinduGamage/RPAL-Interpreter/actions/workflows/testing.yml/badge.svg) ## RPAL-Interpreter + - This project was the culmination of the CS3513-Programming Languages module, which was part of the curriculum offered by the Department of Computer Science & Engineering at the University of Moratuwa. It was completed during the 4th semester of Batch 21. ## Table of Contents @@ -10,31 +11,37 @@ - [Usage](#usage) - [Features](#features) - [Project Structure](#project-structure) + - [Lexical Analyzer](#lexical-analyzer) - [Screener](#screener) - [Parser](#parser) - [CSE Machine](#cse-machine) + - [Project Structure](#project-structure) - [Contributors](#contributors) - [License](#license) - + ## Problem Requirements -- Implement a lexical analyzer and a parser for the RPAL (Right-reference Pedagogic Algorithmic Language). Refer the [RPAL_Lex](https://github.com/malinduGamage/RPAL-Interpreter/blob/main/doc/RPAL_Lex.pdf) for the lexical rules and [RPAL_Grammar](https://github.com/malinduGamage/RPAL-Interpreter/blob/main/doc/RPAL_Grammar.pdf) for the grammar details.Additionally, refer to [About RPAL](https://github.com/malinduGamage/RPAL-Interpreter/blob/main/doc/About%20RPAL.pdf) for information about the RPAL language. + +- Implement a lexical analyzer and a parser for the RPAL (Right-reference Pedagogic Algorithmic Language). Refer the [RPAL_Lex](https://github.com/malinduGamage/RPAL-Interpreter/blob/main/doc/RPAL_Lex.pdf) for the lexical rules and [RPAL_Grammar](https://github.com/malinduGamage/RPAL-Interpreter/blob/main/doc/RPAL_Grammar.pdf) for the grammar details.Additionally, refer to [About RPAL](https://github.com/malinduGamage/RPAL-Interpreter/blob/main/doc/About%20RPAL.pdf) for information about the RPAL language. - The output of the parser should be the Abstract Syntax Tree (AST) for the given input program. -Implement an algorithm to convert the Abstract Syntax Tree (AST) in to Standardize Tree (ST) and implement CSE machine.Refer to the [semantics](https://github.com/malinduGamage/RPAL-Interpreter/blob/main/doc/semantics.pdf) document, which contains the rules for transforming the AST into the ST + Implement an algorithm to convert the Abstract Syntax Tree (AST) in to Standardize Tree (ST) and implement CSE machine.Refer to the [semantics](https://github.com/malinduGamage/RPAL-Interpreter/blob/main/doc/semantics.pdf) document, which contains the rules for transforming the AST into the ST - Program should be able to read an input file which contains a RPAL program and return Output which should match the output of rpal.exe for the relevant program. - For more details, refer the [Project_Requirements](https://github.com/malinduGamage/RPAL-Interpreter/blob/main/doc/ProgrammingProject.pdf) document. ## About our solution - **Programming Language**:python -- **Development & Testing**: Visual Studio Code, Command Line, Cygwin, Pytest, GitHub Actions +- **Development & Testing**: Visual Studio Code, Command Line, Cygwin, Pytest, GitHub Actions ## Usage To use the RPAL-Interpreter, follow these steps: + > ### Prerequisites +> > Your local machine must have Python and pip installed. + 1. Clone the repository to your local machine or download the project source code as a ZIP file. 2. Navigate to the root directory of the project in the command line interface. 3. Install the dependencies by running the following command in the project directory: @@ -44,7 +51,7 @@ pip install -r requirements.txt ``` 4. Put your RPAL test programs in the root directory. We had added the [test.txt](https://github.com/malinduGamage/RPAL-Interpreter/blob/main/test.txt) to the root directory, which contains the sample input program of the [Project_Requirements](https://github.com/malinduGamage/RPAL-Interpreter/blob/main/doc/ProgrammingProject.pdf) document. -Run the main script `main.py` with the file name as an argument: + Run the main script `main.py` with the file name as an argument: ```bash python main.py file_name @@ -78,6 +85,12 @@ To generate the Standardized Tree: python main.py -st file_name ``` +To generate the CSE table: + +```bash +python main.py -ct file_name +``` + #### Using Make Commands (Alternative Method) **Your local machine must be able to run make command** @@ -86,7 +99,7 @@ python main.py -st file_name > For Windows users, for make commad [Cygwin](https://www.cygwin.com/install.html) or similar unix like env tools for execution. -Alternatively, you can use the following make commands: +Alternatively, you can use the following make commands: **Install Dependencies:** @@ -106,6 +119,7 @@ Run all tests (in rpal_tests/rpal_sources/) : ```bash make test_all (in rpal_tests/rpal_sources/) : ``` + Run tests for final result (in rpal_tests/rpal_sources/) : ```bash @@ -129,7 +143,9 @@ make test_st ```bash make all ``` + > #### Note for Python 3 Users +> > If you have both Python 2 and Python 3 installed, you may need to use python3 instead of python in the commands above. Similarly, use pip3 instead of pip for installing packages. ## Features @@ -141,6 +157,7 @@ make all - Executes the RPAL program and produces the output The interpreter consists of the following main components: + ## Project Structure The RPAL interpreter project is structured into several components, each responsible for specific functionalities related to lexical analysis, parsing, interpretation, and error handling. Below is an overview of the project structure and its key components: @@ -214,18 +231,18 @@ RPAL-Interpreter/ │ └── __init__.py # Marks the directory as a Python package | ├── cse_machine/ # Package for parsing functionality -│ ├── apply_operations/ -│ │ ├── apply_bi.py -│ │ ├── apply_un.py +│ ├── apply_operations/ +│ │ ├── apply_bi.py +│ │ ├── apply_un.py │ │ └── __init__.py # Marks the directory as a Python package -│ ├── data_structures/ -│ │ ├── enviroment.py -│ │ ├── stack.py -│ │ ├── control_structure.py -│ │ └── __init__.py -│ ├── utils/ -│ │ ├── STlinearlizer.py -│ │ ├── util.py +│ ├── data_structures/ +│ │ ├── enviroment.py +│ │ ├── stack.py +│ │ ├── control_structure.py +│ │ └── __init__.py +│ ├── utils/ +│ │ ├── STlinearlizer.py +│ │ ├── util.py │ │ └── __init__.py # Marks the directory as a Python package │ ├── machine.py │ └── __init__.py # Marks the directory as a Python package diff --git a/cse_machine/apply_operations/__init__.py b/cse_machine/apply_operations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cse_machine/apply_operations/apply_bin.py b/cse_machine/apply_operations/apply_bin.py new file mode 100644 index 0000000..5079877 --- /dev/null +++ b/cse_machine/apply_operations/apply_bin.py @@ -0,0 +1,293 @@ +# This file contains functions to apply unary and binary operations to operands in the CSE machine. +from utils.control_structure_element import ControlStructureElement + +def apply_binary(cse_machine, rator, rand, binop): + """ + This function applies a binary operation to two operands, based on the specified binary operator. + + Parameters + ---------- + cse_machine: CSEMachine + The CSE machine used to evaluate the expression. + rator: object + The left operand of the binary operation. + rand: object + The right operand of the binary operation. + binop: str + The binary operator to apply. + + Returns + ------- + object + The result of the binary operation. + + Raises + ------ + ValueError + If the binary operator is not recognized. + """ + # Dictionary mapping binary operators to their corresponding functions + binary_operators = { + "aug": lambda cse_machine, rator, rand: apply_aug(cse_machine, rator, rand), + "or": lambda cse_machine, rator, rand: apply_or(cse_machine, rator, rand), + "&": lambda cse_machine, rator, rand: apply_and(cse_machine, rator, rand), + "+": lambda cse_machine, rator, rand: apply_arithmetic(cse_machine, rator, rand, lambda a, b: a + b), + "-": lambda cse_machine, rator, rand: apply_arithmetic(cse_machine, rator, rand, lambda a, b: a - b), + "*": lambda cse_machine, rator, rand: apply_arithmetic(cse_machine, rator, rand, lambda a, b: a * b), + "/": lambda cse_machine, rator, rand: apply_arithmetic(cse_machine, rator, rand, lambda a, b: a // b), + "**": lambda cse_machine, rator, rand: apply_arithmetic(cse_machine, rator, rand, lambda a, b: a ** b), + "gr": lambda cse_machine, rator, rand: apply_comparison(cse_machine, rator, rand, lambda a, b: a > b), + "ge": lambda cse_machine, rator, rand: apply_comparison(cse_machine, rator, rand, lambda a, b: a >= b), + "ls": lambda cse_machine, rator, rand: apply_comparison(cse_machine, rator, rand, lambda a, b: a < b), + "le": lambda cse_machine, rator, rand: apply_comparison(cse_machine, rator, rand, lambda a, b: a <= b), + "eq": lambda cse_machine, rator, rand: apply_eq(cse_machine, rator, rand), + "ne": lambda cse_machine, rator, rand: apply_ne(cse_machine, rator, rand), + "Conc": lambda cse_machine, rator, rand: apply_conc(cse_machine, rator, rand) + } + + # Get the operation function corresponding to the binary operator + operation_function = binary_operators.get(binop) + if operation_function: + # Apply the operation function with the provided operands + return operation_function(cse_machine, rator, rand) + else: + # If the binary operator is not recognized, raise an error + raise ValueError("Invalid binary operation: " + binop) + +# Function to handle the 'aug' binary operator +def apply_aug(cse_machine, rator, rand): + """ + This function applies a binary operation to two operands, based on the specified binary operator. + + Parameters + ---------- + cse_machine : CSEMachine + The CSE machine used to evaluate the expression. + rator : object + The left operand of the binary operation. + rand : object + The right operand of the binary operation. + + Returns + ------- + object + The result of the binary operation. + + Raises + ------ + ValueError + If the binary operator is not recognized. + """ + if rator.type == "nil" : + return ControlStructureElement("tuple", [rand]) + elif rand.type == "nil": + if rator.type == "tuple": + # If the right operand is "nil", return the left operand + rator.value.append(rand) + return rator + return rator + elif rator.type in ["tuple","ID","INT","STR","bool"] and rand.type in ["tuple","ID","INT","STR","bool"]: + if isinstance(rator.value, list) : + ls = rator.value.copy() + ls.append(rand) + res = ControlStructureElement("tuple", ls) + return res + return ControlStructureElement("tuple", [rator, rand]) + else: + return cse_machine._error_handling.handle_error("Cannot augment a non tuple (2).") + +# Function to handle the 'or' binary operator +def apply_or(cse_machine, rator, rand): + """ + This function applies a binary operation to two operands, based on the specified binary operator. + + Parameters + ---------- + cse_machine : CSEMachine + The CSE machine used to evaluate the expression. + rator : object + The left operand of the binary operation. + rand : object + The right operand of the binary operation. + + Returns + ------- + object + The result of the binary operation. + + Raises + ------ + ValueError + If the binary operator is not recognized. + """ + if isinstance(rator, bool) and isinstance(rand, bool): + # If both operands are boolean, return the logical OR of them + return rator or rand + else: + # Otherwise, raise an error + raise cse_machine._error_handling.handle_error("Invalid value used in logical expression 'or'") + +# Function to handle the 'and' binary operator +def apply_and(cse_machine, rator, rand): + """ + This function applies a binary operation to two operands, based on the specified binary operator. + + Parameters + ---------- + cse_machine : CSEMachine + The CSE machine used to evaluate the expression. + rator : object + The left operand of the binary operation. + rand : object + The right operand of the binary operation. + + Returns + ------- + object + The result of the binary operation. + + Raises + ------ + ValueError + If the binary operator is not recognized. + """ + if isinstance(rator, bool) and isinstance(rand, bool): + # If both operands are boolean, return the logical AND of them + return rator and rand + else: + # Otherwise, raise an error + raise ValueError("Illegal Operands for '&'") + +# Function to handle the 'eq' binary operator +def apply_eq(cse_machine, rator, rand): + """ + This function applies a binary operation to two operands, based on the specified binary operator. + + Parameters + ---------- + cse_machine : CSEMachine + The CSE machine used to evaluate the expression. + rator : object + The left operand of the binary operation. + rand : object + The right operand of the binary operation. + + Returns + ------- + bool + The result of the binary operation. + + Raises + ------ + ValueError + If the binary operator is not recognized. + """ + if type(rator) == type(rand): + # If the types of both operands are the same, return whether they are equal + return rator == rand + else: + # Otherwise, raise an error + raise cse_machine._error_handling.handle_error("Illegal Operands for 'eq'") + +# Function to handle the 'ne' binary operator +def apply_ne(cse_machine, rator, rand): + """ + This function applies a binary operation to two operands, based on the specified binary operator. + + Parameters + ---------- + cse_machine : CSEMachine + The CSE machine used to evaluate the expression. + rator : object + The left operand of the binary operation. + rand : object + The right operand of the binary operation. + + Returns + ------- + bool + The result of the binary operation. + + Raises + ------ + ValueError + If the binary operator is not recognized. + """ + if type(rator) == type(rand): + # If the types of both operands are the same, return whether they are not equal + return rator != rand + else: + # Otherwise, raise an error + raise cse_machine._error_handling.handle_error("Illegal Operands for 'ne'") + +# Function to handle arithmetic operations +def apply_arithmetic(cse_machine, rator, rand, operation): + """ + This function applies an arithmetic operation to two operands, based on the specified operation function. + + Parameters + ---------- + cse_machine : CSEMachine + The CSE machine used to evaluate the expression. + rator : object + The left operand of the arithmetic operation. + rand : object + The right operand of the arithmetic operation. + operation : Callable[[int, int], int] + The arithmetic operation to apply. + + Returns + ------- + int + The result of the arithmetic operation. + + Raises + ------ + ValueError + If the operands are not integers or the operation is not a function. + """ + if isinstance(rator, int) and isinstance(rand, int): + # If both operands are integers, apply the specified arithmetic operation + return operation(rator, rand) + else: + # Otherwise, raise an error + raise cse_machine._error_handler.handle_error("Illegal Operands for Arithmetic Operation") + +def apply_conc(cse_machine,rator,rand): + if isinstance(rator, str) and isinstance(rand, str): + return rator + rand + else: + raise cse_machine._error_handler.handle_error("Non-strings used in conc call") + +# Function to handle comparison operations +def apply_comparison(cse_machine, rator, rand, operation): + """ + This function applies a comparison operation to two operands, based on the specified operation function. + + Parameters + ---------- + cse_machine : CSEMachine + The CSE machine used to evaluate the expression. + rator : object + The left operand of the comparison operation. + rand : object + The right operand of the comparison operation. + operation : Callable[[int, int], int] + The comparison operation to apply. + + Returns + ------- + bool + The result of the comparison operation. + + Raises + ------ + ValueError + If the operands are not integers or the operation is not a function. + """ + if (isinstance(rator, int) and isinstance(rand, int)) or (isinstance(rator, str) and isinstance(rand, str)): + # If both operands are integers, apply the specified comparison operation + return operation(rator, rand) + else: + # Otherwise, raise an error + raise cse_machine._error_handler.handle_error("Illegal Operands for 'gr'") diff --git a/cse_machine/apply_operations/apply_un.py b/cse_machine/apply_operations/apply_un.py new file mode 100644 index 0000000..54f52a4 --- /dev/null +++ b/cse_machine/apply_operations/apply_un.py @@ -0,0 +1,166 @@ +#cse_machine/binary_operations/apply_bin.py + +# Description +# This module defines functions to apply binary operations to operands in the CSE (Compiler, Symbolic, Expression) machine. + +# Usage +# This module can be imported and used to apply binary operations to operands in the CSE machine. + +from utils.control_structure_element import ControlStructureElement +def apply_unary(cse_machine, rator_e, unop): + """ + Apply a unary operation to an operand. + + Args: + cse_machine (CSEMachine): The CSE machine instance. + rator_e (RatorExpr): The operand expression. + unop (str): The unary operation to apply. + + Returns: + Expr: The result of the operation. + + Raises: + ValueError: If the unary operation is not recognized. + """ + rator = rator_e.value + # Dictionary mapping binary operators to their corresponding functions + unary_operators = { + "Print": lambda cse_machine, operand: apply_print(cse_machine, operand), + "Isstring": lambda cse_machine, operand: operand.type == "STR", + "Isinteger": lambda cse_machine, operand: operand.type == "INT" , + "Istruthvalue": lambda cse_machine, operand: operand.type == "bool", + "Isfunction": lambda cse_machine, operand: operand.type == "lambda", + "Null": lambda cse_machine, operand: operand.type == "nil", + "Istuple": lambda cse_machine, operand: isinstance(operand.value, list) or operand.type == "nil", + "Order": lambda cse_machine, operand: apply_order(cse_machine, operand), + "Stern": lambda cse_machine, operand: apply_stern(cse_machine, operand.value), + "Stem": lambda cse_machine, operand: apply_stem(cse_machine, operand.value), + "ItoS": lambda cse_machine, operand: str(operand.value) if isinstance(operand.value, int) and not isinstance(operand.value, bool) else cse_machine._error_handler.handle_error("CSE : Invalid unary operation"), + "neg": lambda cse_machine, operand: -operand.value if isinstance(operand.value, int) else cse_machine._error_handler.handle_error("CSE : Invalid unary operation"), + "not": lambda cse_machine, operand: not operand.value if isinstance(operand.value, bool) else cse_machine._error_handler.handle_error("CSE : Invalid unary operation"), + } + # Get the operation function corresponding to the binary operator + operation_function = unary_operators.get(unop) + if operation_function: + # Apply the operation function with the provided operands + return operation_function(cse_machine, rator_e) + else: + # If the binary operator is not recognized, raise an error + raise ValueError("Invalid binary operation: " + unop) + +# Function to apply the Print unary operator +def apply_print(cse_machine, operand): + """ + Apply the Print unary operation to an operand. + + Args: + cse_machine (CSEMachine): The CSE machine instance. + operand (Expr): The operand expression. + + Returns: + Expr: A dummy value. + + """ + element = operand.value + # Define the covertToString function + def covert_to_string(element): + if isinstance(element, list): + out = "" + return convert_list(element,out) + elif element == "lambda": + x = "".join(x for x in operand.bounded_variable) + k = str(operand.control_structure) + return "[lambda closure: " + x + ": " + k + "]" + elif isinstance(element, bool): + return "true" if element else "false" + elif isinstance(element, str): + return element + elif isinstance(element, int): + return str(element) + elif element == None: + return "nil" + else: + raise TypeError("Unknown element type.") + + def convert_list(element,out): + if isinstance(element, list): + out += "(" + for el in element: + out = convert_list(el,out) + out = out[:-2] + ")" + else: + if isinstance(element.value, list): + out += "(" + for el in element.value: + out = convert_list(el,out) + out = out[:-2] + "), " + else: + out += str(element.value) + ", " + return out + # convert the element to a string + cse_machine._outputs.append(covert_to_string(element).replace("\\n", "\n").replace("\\t", "\t")) + + # Return a dummy value + return "dummy" + + +# Function to apply the Order unary operator +def apply_order(cse_machine, operand): + """ + Apply the Order unary operation to an operand. + + Args: + cse_machine (CSEMachine): The CSE machine instance. + operand (Expr): The operand expression. + + Returns: + int: The ASCII value of the first character of the operand. + + Raises: + ValueError: If the operand is not a string. + """ + if isinstance(operand.value, list): + return len(operand.value) + elif operand.type == "nil": + return 0 + else: + cse_machine._error_handler.handle_error("CSE : Invalid unary operation") + +# Function to apply the Stern unary operator +def apply_stern(cse_machine, operand): + """ + Apply the Stern unary operation to an operand. + + Args: + cse_machine (CSEMachine): The CSE machine instance. + operand (Expr): The operand expression. + + Returns: + str: The first character of the operand. + + Raises: + ValueError: If the operand is not a string or is empty. + """ + if isinstance(operand, str) and len(operand) >= 1: + return operand[1:] + else: + cse_machine._error_handler.handle_error("CSE : Invalid unary operation") + +def apply_stem(cse_machine, operand): + """ + Apply the Stem unary operation to an operand. + + Args: + cse_machine (CSEMachine): The CSE machine instance. + operand (Expr): The operand expression. + + Returns: + str: The remaining characters of the operand. + + Raises: + ValueError: If the operand is not a string or is empty. + """ + if isinstance(operand, str) and len(operand) >= 1: + return operand[0] + else: + cse_machine._error_handler.handle_error("CSE : Invalid unary operation") \ No newline at end of file diff --git a/cse_machine/data_structures/__init__.py b/cse_machine/data_structures/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cse_machine/data_structures/control_structure.py b/cse_machine/data_structures/control_structure.py new file mode 100644 index 0000000..7df880c --- /dev/null +++ b/cse_machine/data_structures/control_structure.py @@ -0,0 +1,31 @@ +#cse_machine/data_structures/control_structure.py + +# Description +# This module defines a control structure class for the CSE (Compiler, Symbolic, Expression) machine. + +# Usage +# This module contains the ControlStructure class, which represents a control structure in the CSE machine. + + +from utils.stack import Stack + +class ControlStructure(Stack): + """ + Represents a control structure in the CSE (Compiler, Symbolic, Expression) machine. + Inherits from Stack class. + + Attributes: + elements (list): List of elements in the control structure. + index (int): Index of the control structure. + """ + + def __init__(self, index): + """ + Initializes a new control structure. + + Args: + index (int): Index of the control structure. + """ + super().__init__() + self.elements = self.items # Alias for the items attribute inherited from Stack + self.index = index diff --git a/cse_machine/data_structures/enviroment.py b/cse_machine/data_structures/enviroment.py new file mode 100644 index 0000000..37a39af --- /dev/null +++ b/cse_machine/data_structures/enviroment.py @@ -0,0 +1,81 @@ +# cse_machine/data_structures/enumeration.py + +# Description +# This module defines an enumeration class for the CSE (Compiler, Symbolic, Expression) machine. +# The enumeration class is used to represent different types of data and operations within the machine. + +# Usage +# This module can be imported and used to define and handle enumerations in the CSE machine. + + +from collections import defaultdict + +class Environment: + index = -1 + + # List of initial variables + INITIAL_VARIABLES = [ + "Print", "Isstring", "Isinteger", "Istruthvalue", + "Istuple", "Isfunction", "Null", "Order", "Stern", + "Stem", "ItoS", "neg", "not", "Conc" + ] + + def __init__(self, parent=None): + """ + Initialize a new environment. + + Args: + parent (Environment, optional): The parent environment. Defaults to None. + """ + Environment.index += 1 + self.index = Environment.index + self._environment = defaultdict(lambda: [None, None]) # name: [type, value] + self.parent = parent + + # Initialize initial variables if this is the root environment + if self.index == 0: + self._initialize_initial_vars() + + def _initialize_initial_vars(self): + """ + Initialize initial variables. + """ + initial_vars = {var: None for var in self.INITIAL_VARIABLES} + self._environment.update(initial_vars) + + def add_var(self, name, type, value): + """ + Add a variable to the environment. + + Args: + name (str): The name of the variable. + type (str): The type of the variable. + value (any): The value of the variable. + """ + self._environment[name] = [type, value] + + def add_child(self, branch): + """ + Add a child branch to the environment. + + Args: + branch (Environment): The child branch environment. + """ + self.children.append(branch) + + def set_parent(self, parent): + """ + Set the parent environment. + + Args: + parent (Environment): The parent environment. + """ + self.parent = parent + # Update the parent reference in the _environment dictionary + self._environment['__parent__'] = parent._environment if parent else None + + def reset_index(self): + """ + Reset the index of the environment. + """ + Environment.index = -1 \ No newline at end of file diff --git a/cse_machine/data_structures/stack.py b/cse_machine/data_structures/stack.py new file mode 100644 index 0000000..8cb4567 --- /dev/null +++ b/cse_machine/data_structures/stack.py @@ -0,0 +1,32 @@ +# cse_machine/data_structures/stack.py + +# Description +# This module defines a custom stack class for the CSE (Compiler, Symbolic, Expression) machine. + +# Usage +# This module can be imported and used to create a custom stack class for the CSE machine. + +from utils.stack import Stack +from cse_machine.data_structures.enviroment import Environment + +class STACK(Stack): + """ + A custom stack class that extends the Stack class and allows accessing the nearest Environment object. + """ + + def __init__(self): + """ + Initialize an empty stack. + """ + super().__init__() + #self._environments = Stack() # Stack to store Environment objects + #self.stack = Stack() # Stack to store Environment objects + + def current_environment(self): + """ + Return the nearest Environment object from the stack without removing it. + """ + for item in reversed(self.items): + if item.type == "env_marker": + return item.env + diff --git a/cse_machine/machine.py b/cse_machine/machine.py index da86dc3..7b0c8cd 100644 --- a/cse_machine/machine.py +++ b/cse_machine/machine.py @@ -1,8 +1,469 @@ -from errors_handling.error_handler import ErrorHandler +# cse_machine/machine.py + +""" +Control Structure Environment (CSE) Machine for Executing RPAL Programs. + +Description: +This file contains the implementation of the Control Structure Environment (CSE) machine, which is responsible for executing RPAL programs by interpreting the Standardized Tree (ST) generated by the parser. It contains the CSEMachine class with methods for executing the ST and applying the necessary control structure rules. + +Usage: +The CSEMachine class is used to interpret RPAL programs by executing the Standardized Tree (ST) representation generated during the parsing phase. It provides methods to execute the ST and apply control structure rules to evaluate expressions and perform operations defined in the RPAL language. + +Example Usage: +1. Create an instance of the CSEMachine class: + cse_machine = CSEMachine() + +2. Initialize the machine with the control structures and environment: + cse_machine.initialize() + +3. Execute the RPAL program by passing the Standardized Tree (ST): + st_tree = generate_standardized_tree(rpal_program) + cse_machine.execute(st_tree) + +4. Retrieve the output of the program: + output = cse_machine.get_output() + print("Output:", output) +""" + + +from errors_handling.cse_error_handler import CseErrorHandler +from cse_machine.data_structures.control_structure import ControlStructure +from cse_machine.data_structures.enviroment import Environment +from cse_machine.data_structures.stack import Stack +from cse_machine.utils.STlinearizer import Linearizer +from cse_machine.utils.util import add_table_data, print_cse_table , var_lookup , raw +from cse_machine.apply_operations.apply_bin import apply_binary +from cse_machine.apply_operations.apply_un import apply_unary +from utils.node import Node +from utils.control_structure_element import ControlStructureElement + class CSEMachine: + """ + Control Structure Environment (CSE) Machine for executing RPAL programs. + + Attributes: + _error_handler (CseErrorHandler): Error handler instance for managing errors during execution. + control_structures (list): List of control structures extracted from the Standardized Tree (ST). + environment_tree (Environment): Environment tree representing the current execution environment. + current_env (Environment): Reference to the current environment in the environment tree. + stack (Stack): Stack for managing the execution stack. + control (Stack): Stack for managing the control structures during execution. + _linearizer (Linearizer): Linearizer instance for converting the ST to linear form. + binary_operator (set): Set of binary operators supported by the RPAL language. + unary_operators (set): Set of unary operators supported by the RPAL language. + _outputs (list): List to store the output generated during execution. + table_data (list): List to store data for generating the execution table. + """ + def __init__(self): - print("Import working: CSEMachine imported successfully.") - self._machine = None + """ + Initialize the CSEMachine with necessary components. + """ + self._error_handler = CseErrorHandler(self) + self.control_structures = None + self.environment_tree = Environment() + self.current_env = self.environment_tree + self.stack = Stack() + self.control = Stack() + self._linearizer = Linearizer() + self._outputs = [] + self.table_data = [] + self.binary_operator = {"+", "-", "/", "*", "**", "eq", "ne", "gr", "ge", "le",">", "<", ">=", "<=", "or", "&", "aug", "ls", "Conc"} + self.unary_operators = { + "Print", "Isstring", "Isinteger", "Istruthvalue", "Isfunction", "Null", + "Istuple", "Order", "Stern", "Stem", "ItoS", "neg", "not", "$ConcPartial" + } + + def initialize(self): + """ + Initialize the CSEMachine with necessary components. + + :return: None + """ + # Push an environment marker onto the stack and control structures + env_marker = ControlStructureElement("env_marker", "env_marker", None, None, self.current_env) + self.stack.push(env_marker) + self.control.push(env_marker) + + # Push elements from the first control structure onto the control stack + if self.control_structures: + elements = self.control_structures[0].elements + for element in elements: + self.control.push(element) + else: + # Handle the case when control_structures is empty + self._error_handler.handle_error("Control structures are empty") + + def execute(self, st_tree): + """ + Execute the given Standardized Tree (ST). + + Args: + st_tree (Node): The root node of the Standardized Tree (ST) to execute. + """ + + # Get the linearized control structures from the ST + self.control_structures = self._linearizer.linearize(st_tree) + + # Initialize the CSE machine + self.initialize() + + # Execute the ST + while not self.control.is_empty(): + + control_top = self.control.peek() + stack_top = self.stack.peek() + + if control_top.type in ['ID','STR','INT','bool','tuple','Y*','nil','dummy']: + self.CSErule1() + elif control_top.type == "lambda": + self.CSErule2() + elif control_top.type == "env_marker": + self.CSErule5() + elif control_top.value in self.binary_operator and self.stack.size() >= 2: + self.CSErule6() + elif control_top.value in self.unary_operators and self.stack.size() >= 1: + self.CSErule7() + elif control_top.type == "beta" and self.stack.size() >= 1: + self.CSErule8() + elif control_top.type == "tau": + self.CSErule9() + elif control_top.type == "gamma" and stack_top.type == "tuple": + self.CSErule10() + elif control_top.type == "gamma" and stack_top.type == "Y*": + self.CSErule12() + elif control_top.type == "gamma" and stack_top.type == "eta": + self.CSErule13() + elif control_top.type == "gamma" and stack_top.type == "lambda": + if len(stack_top.bounded_variable) > 1: + self.CSErule11() + else: + self.CSErule4() + elif control_top.type == "gamma" and stack_top.type == "ConcPartial": + self.Concpartial() + else: + self._error_handler.handle_error("CSE : Invalid control structure") + + def CSErule1(self): + """ + CSE rule 1: If the top of the control stack is a variable, constant, or tuple, + push it onto the stack. If the top of the control stack is a control structure, + check whether it is a variable or a lambda expression. If it is a variable, + look up its value in the environment and push it onto the stack. If it is a + lambda expression, push it onto the stack. + """ + self._add_table_data("1") + control_top = self.control.peek() + if control_top.type in ['STR','INT','bool','tuple','Y*','nil','dummy']: + self.stack.push(self.control.pop()) + else : + item = self.control.pop() + var_name = item.value + var = self._var_lookup(var_name) + if var[0] == "eta" or var[0] == "lambda": + self.stack.push(var[1]) + else : + self.stack.push(ControlStructureElement(var[0],var[1])) + + def CSErule2(self): + """ + CSE rule 2: If the top of the control stack is a lambda expression, + push it onto the stack. Set the environment of the lambda expression to the current environment. + """ + self._add_table_data("2") + lambda_ = self.control.pop() + lambda_.env = self.current_env + self.stack.push(lambda_) + + def CSErule3(self): + self._add_table_data("3") + pass + + def CSErule4(self): + """ + CSE rule 4: If the top of the control stack is a lambda expression, + push it onto the stack. Set the environment of the lambda expression to the current environment. + """ + self._add_table_data("4") + if self.current_env.index >= 2000: + self._error_handler.handle_error("CSE : Environment limit exceeded") + self.control.pop() + lambda_ = self.stack.pop() + rand = self.stack.pop() + new_env = Environment() + if rand.type == "eta" or rand.type == "lambda": + new_env.add_var(lambda_.bounded_variable[0],rand.type,rand) + elif rand.type in ["tuple","INT","bool","STR","nil"]: + new_env.add_var(lambda_.bounded_variable[0],rand.type,rand.value) + else: + self._error_handler.handle_error("CSE : Invalid type") + new_env.parent = lambda_.env + + self.current_env = new_env + + env_marker = ControlStructureElement("env_marker","env_marker",None,None,new_env) + + self.control.push(env_marker) + + for element in self.control_structures[lambda_.control_structure].elements: + self.control.push(element) + + self.stack.push(env_marker) + + def CSErule5(self): + """ + CSE rule 5: If the top of the control stack is an environment marker, + pop it off the stack and set the current environment to the environment + it represents. Then, pop the value off the stack and push it back on. + Finally, iterate over the entire stack, starting from the bottom, and + check if the current element is an environment marker. If it is, set the + current environment to the environment it represents and break out of the loop. + If the environments do not match, raise an error. + + Parameters: + None + + Returns: + None + + Raises: + CseError: If the environments do not match + """ + self._add_table_data("5") + env = self.control.pop().env + value = self.stack.pop() + if env == self.stack.pop().env: + self.stack.push(value) + for element in reversed(self.stack.whole_stack()): + if element.type == "env_marker": + self.current_env = element.env + break + else: + self._error_handler.handle_error("CSE : Invalid environment") + + def CSErule6(self): + """ + CSE rule 6: If the top of the control stack is a binary operation, + pop two elements from the stack, apply the binary operation to the + two popped elements, and push the result back onto the stack. + If the top of the control stack is "aug", pop two elements from the stack, + apply the addition operator to the two popped elements, and push the result back onto the stack. + If the top of the control stack is "Conc", pop two elements from the stack, + check if both elements are of type "STR", and if so, concatenate the two strings and push the result back onto the stack. + If one of the two elements is not of type "STR", replace the element with type "ConcPartial" and push the other element back onto the stack. + If both elements are not of type "STR", raise an error. + """ + self._add_table_data("6") + binop = self.control.pop().value + rator = self.stack.pop() + rand = self.stack.pop() + if binop == "aug": + self.stack.push(self._apply_binary(rator,rand,binop)) + elif binop == "Conc": + if rator.type == "STR" and rand.type == "STR": + result =self._apply_binary(rator.value,rand.value,binop) + self.stack.push(ControlStructureElement("STR",result)) + while self.control.peek().type == "gamma": + self.control.pop() + elif rator.type == "STR": + rator.type = "ConcPartial" + self.stack.push(rand) + self.stack.push(rator) + while self.control.peek().type == "gamma": + self.control.pop() + else: + self._error_handler.handle_error("CSE : Invalid type for concatenation") + else: + rator = rator.value + rand = rand.value + result = self._apply_binary(rator,rand,binop) + typ = None + if type(result) == bool: + typ = "bool" + else: + typ = "INT" + self.stack.push(ControlStructureElement(typ,result)) + + + def CSErule7(self): + """ + CSE rule 7: If the top of the control stack is a unary operation, + pop one element from the stack, apply the unary operation to the + popped element, and push the result back onto the stack. + If the top of the control stack is "neg", pop one element from the stack, + apply the negation operator to the popped element, and push the result back onto the stack. + If the top of the control stack is "not", pop one element from the stack, + """ + + self._add_table_data("7") + unop = self.control.pop().value + rator_e = self.stack.pop() + result = self._apply_unary(rator_e,unop) + res_type = None + if type(result) == bool: + res_type = "bool" + elif type(result) == str: + res_type = "STR" + else : + res_type = "INT" + while self.control.peek().type == "gamma": + self.control.pop() + self.stack.push(ControlStructureElement(res_type,result)) + + def CSErule8(self): + """ + CSE rule 8: If the top of the control stack is a boolean value, + pop it off the stack and check if it is True or False. If it is True, + pop two elements from the stack, check if the second element is a control structure, + and if so, replace the second element with the elements from the control structure. + Then, pop the first element off the stack and push it back on. + If the second element is not a control structure, raise an error. + If the boolean value is False, pop two elements from the stack, + check if the first element is a control structure, and if so, replace the first element + with the elements from the control structure. Then, pop the second element off the stack + and push it back on. If the first element is not a control structure, raise an error. + """ + self._add_table_data("8") + val = self.stack.pop().value + if val == True : + self.control.pop() + self.control.pop() + delta = self.control.pop() + for element in self.control_structures[delta.control_structure].elements: + self.control.push(element) + elif val == False: + self.control.pop() + delta = self.control.pop() + self.control.pop() + for element in self.control_structures[delta.control_structure].elements: + self.control.push(element) + else: + self._error_handler.handle_error("CSE : Invalid type for condition") + + def CSErule9(self): + """ + CSE rule 9: If the top of the control stack is a "tau" node, + pop it off the stack and create a new tuple with the next "n" elements + on the stack. Push the tuple back onto the stack. + """ + self._add_table_data("9") + tau = self.control.pop() + n = tau.value + tup = [] + for i in range(n): + tup.append(self.stack.pop()) + self.stack.push(ControlStructureElement("tuple",tup)) + + def CSErule10(self): + """ + CSE rule 10: If the top of the control stack is a tuple, pop it off the stack, + retrieve the index from the stack, and retrieve the element at the given index from the tuple. + If the index is out of bounds, raise an error. Push the retrieved element back onto the stack. + """ + self._add_table_data("10") + self.control.pop() + l = self.stack.pop() + index = self.stack.pop() + if index.type != "INT": + self._error_handler.handle_error("CSE : Invalid index") + index = index.value-1 + self.stack.push(l.value[index]) + + def CSErule11(self): + """ + CSE rule 11: If the top of the control stack is a lambda expression, + pop it off the stack and retrieve the bounded variables, control structure, and environment from the lambda expression. + Create a new environment with the same parent as the environment of the lambda expression. + Pop the next "n" elements from the stack, where "n" is the number of arguments of the lambda expression. + For each element popped from the stack, add it to the new environment with the same type as the element. + Set the current environment to the new environment. + Push an environment marker onto the stack with the new environment as its environment. + Push the elements from the control structure associated with the lambda expression onto the control stack. + """ + self._add_table_data("11") + self.control.pop() + lambda_ = self.stack.pop() + var_list = lambda_.bounded_variable + k = lambda_.control_structure + c = lambda_.env + + new_env = Environment() + rand = self.stack.pop() + + if len(var_list) != len(rand.value): + self._error_handler.handle_error("CSE : Invalid number of arguments") + + for i in range(len(var_list)): + if rand.value[i].type == "eta" or rand.value[i].type == "lambda": + new_env.add_var(var_list[i],rand.value[i].type,rand.value[i]) + else: + new_env.add_var(var_list[i],rand.value[i].type,rand.value[i].value) + + new_env.parent = c + self.current_env = new_env + env_marker = ControlStructureElement("env_marker","env_marker",None,None,new_env) + self.stack.push(env_marker) + self.control.push(env_marker) + + for element in self.control_structures[k].elements: + self.control.push(element) + + def CSErule12(self): + """ + CSE rule 12: If the top of the control stack is a "tau" node, + pop it off the stack and create a new tuple with the next "n" elements + on the stack. Push the tuple back onto the stack. + """ + self._add_table_data("12") + self.control.pop() + self.stack.pop() + lambda_ = self.stack.pop() + if lambda_.type != "lambda": + self._error_handler.handle_error("CSE : expected lambda") + eta = ControlStructureElement("eta","eta",lambda_.bounded_variable,lambda_.control_structure,lambda_.env) + self.stack.push(eta) + + def CSErule13(self): + """ + CSE rule 13: If the top of the control stack is a "tau" node, + pop it off the stack and create a new tuple with the next "n" elements + on the stack. Push the tuple back onto the stack. + """ + self._add_table_data("13") + self.control.push(ControlStructureElement("gamma","gamma")) + eta = self.stack.peek() + self.stack.push(ControlStructureElement("lambda","lambda",eta.bounded_variable,eta.control_structure,eta.env)) + + def Concpartial(self): + rator = self.stack.pop() + rand = self.stack.pop() + if rand.type == "STR": + result = self._apply_binary(rator.value,rand.value,"Conc") + self.stack.push(ControlStructureElement("STR",result)) + while self.control.peek().type == "gamma": + self.control.pop() + else: + self._error_handler.handle_error("CSE : Invalid type for concatenation") + + + def _var_lookup(self , var_name): + return var_lookup(self, var_name) + + def _apply_binary(self , rator , rand , binop): + return apply_binary(self, rator, rand, binop) + + def _apply_unary(self , rator , unop): + return apply_unary(self, rator, unop) + + def _add_table_data(self, rule): + add_table_data(self, rule) + + def _print_cse_table(self): + print_cse_table(self) - \ No newline at end of file + def _generate_output(self): + return "".join(self._outputs)+"\n" + + def _generate_raw_output(self): + return raw(self._generate_output()) \ No newline at end of file diff --git a/cse_machine/utils/STlinearizer.py b/cse_machine/utils/STlinearizer.py new file mode 100644 index 0000000..6880b83 --- /dev/null +++ b/cse_machine/utils/STlinearizer.py @@ -0,0 +1,143 @@ +#cse_machine/utils/STlinearizer.py + +# Description +# This module defines a linearizer class for the CSE (Compiler, Symbolic, Expression) machine. + +# Usage +# This module can be imported and used to create a linearizer class for the CSE machine. + + +from cse_machine.data_structures.control_structure import ControlStructure +from utils.control_structure_element import ControlStructureElement + + +class Linearizer: + """ + This class defines a linearizer for the CSE machine. + + Usage: + >>> from cse_machine.data_structures.control_structure import ControlStructure + >>> from utils.control_structure_element import ControlStructureElement + >>> linearizer = Linearizer() + >>> st_tree =... # input syntax tree + >>> linearizer.linearize(st_tree) + """ + def __init__(self): + """ + Initialize the linearizer. + """ + self.control_structures = [] + + def linearize(self,st_tree): + """ + Linearize the input syntax tree. + + Args: + st_tree (SyntaxTreeNode): The input syntax tree. + + Returns: + list[ControlStructure]: The linearized control structures. + """ + self.preorder_traversal(st_tree, 0) + + return self.control_structures + + def preorder_traversal(self, root , index): + """ + Perform a preorder traversal on the syntax tree. + + Args: + root (SyntaxTreeNode): The root of the syntax tree. + index (int): The index of the current control structure. + """ + + if len(self.control_structures) <= index: + self.control_structures.append(ControlStructure(index)) + + if not root.children: + self.control_structures[index].push(ControlStructureElement(self.filter(root.data)[0], self.filter(root.data)[1])) + return + + if root.data == "lambda": + + if root.children[0].data == ",": + var_list = [] + for child in root.children[0].children: + var_list.append(self.filter(child.data)[1]) + self.control_structures[index].push(ControlStructureElement("lambda", "lambda", var_list, len(self.control_structures))) + else: + self.control_structures[index].push(ControlStructureElement("lambda", "lambda", [self.filter(root.children[0].data)[1]], len(self.control_structures))) + self.preorder_traversal(root.children[1], len(self.control_structures)) + + elif root.data == "tau": + self.control_structures[index].push(ControlStructureElement("tau", len(root.children))) + for child in root.children: + self.preorder_traversal(child, index) + + elif root.data == "->": + self.control_structures[index].push(ControlStructureElement("delta", "delta",None, len(self.control_structures))) + self.preorder_traversal(root.children[1], len(self.control_structures)) + self.control_structures[index].push(ControlStructureElement("delta", "delta",None, len(self.control_structures))) + self.preorder_traversal(root.children[2], len(self.control_structures)) + self.control_structures[index].push(ControlStructureElement("beta", "beta")) + self.preorder_traversal(root.children[0], index) + + else: + self.control_structures[index].push(ControlStructureElement(self.filter(root.data)[0], self.filter(root.data)[1])) + + self.preorder_traversal(root.children[0], index) + if len(root.children) > 1: + self.preorder_traversal(root.children[1], index) + + def filter(self,token): + """ + Filter the input tokens. + + Args: + token (str): The input token. + + Returns: + list[str]: The filtered tokens. + """ + output = [] + if token[0] == "<": + if len(token)>3 and token[1:3] == "ID": + if token[4:-1] in ["Conc","Print","Stern","Stem","Isstring","Isinteger","Istruthvalue","Isfunction","Null","Istuple","Order","ItoS","not","neg" ]: + output = [token[4:-1], token[4:-1]] + else: + output = ["ID", token[4:-1]] + elif len(token)>4 and token[1:4] == "INT": + output = ["INT", int(token[5:-1])] + elif len(token)>4 and token[1:4] == "STR": + output = ["STR", token[6:-2]] + elif token[1:-1] == "true": + output = ["bool",True] + elif token[1:-1] == "false": + output = ["bool",False] + elif token[1:-1] == "nil": + output = ["nil",None] + else: + output = [token[1:-1], token[1:-1]] + else: + output = [token, token] + return output + + def print_control_structures(self): + """ + Print the control structures. + """ + print('\n') + for structure in self.control_structures: + print(f"δ_{structure.index} = ",end="") + for element in structure.elements: + if element.type == "lambda": + print(f"λ_{element.control_structure}{element.bounded_variable}",end=" ") + elif element.type == "delta": + print(f"δ_{element.control_structure}",end=" ") + elif element.type == "tau": + print(f"{element.type}[{element.value}]",end=" ") + elif element.type == "gamma": + print("γ",end=" ") + else: + print(element.value,end=" ") + print("\n") diff --git a/cse_machine/utils/__init__.py b/cse_machine/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cse_machine/utils/util.py b/cse_machine/utils/util.py new file mode 100644 index 0000000..76d66d2 --- /dev/null +++ b/cse_machine/utils/util.py @@ -0,0 +1,116 @@ + +def var_lookup(cse_machine , var_name): + """ + Searches the current environment for a variable with the given name. + + Args: + cse_machine (CSE_Machine): The CSE machine that is currently running. + var_name (str): The name of the variable to search for. + + Returns: + Any: The value of the variable, or None if the variable was not found. + + Raises: + CSEError: If the variable was not found and no default value was provided. + """ + env_pointer = cse_machine.current_env + while env_pointer: + if var_name in env_pointer._environment: + out = env_pointer._environment[var_name] + return out + env_pointer = env_pointer.parent + else: + cse_machine._error_handler.handle_error(f"CSE : Variable [{var_name}] not found in the environment") + +def add_table_data(cse_machine,rule): + """Add the given rule to the CSE table. + + Args: + cse_machine (CSE_Machine): The CSE machine that is currently running. + rule (Rule): The rule to add to the table. + """ + table_data = cse_machine.table_data + table_data.append((rule,cse_machine.control.whole_stack()[:],cse_machine.stack.whole_stack()[:],[cse_machine.current_env.index][:])) + +def print_cse_table(cse_machine): + """Print the CSE table. + + Args: + cse_machine (CSE_Machine): The CSE machine that is currently running. + """ + table_data = cse_machine.table_data + cse_machine._add_table_data("") + control_width = 80 + stack_width = 60 + total_width = control_width + stack_width + 16 + print("\nCSE TABLE") + print("\nRULE | CONTROL" + " " * (control_width-6) + "|"+" "*(stack_width-5)+" STACK " + "| ENV") + print("-" * total_width) + for data in table_data: + rule = f"{data[0]:<2} |" + control = " ".join(str(element_val(element)) for element in data[1]) + stack = " ".join(str(element_val(element)) for element in data[2][::-1]) + env = f" {data[-1][0]}" + l = len(control) + control_str = f"{control[max(0, l - control_width):]:<{control_width}}" + stack_str = f"{stack[:stack_width]:>{stack_width}}" + + print(f" {rule} {control_str} | {stack_str} | {env}") + print("-" * total_width ) + +def element_val(element): + """Get the value of a given element. + + Args: + element (Element): The element to get the value of. + + Returns: + Any: The value of the element. + """ + if element.type == "tuple": + output = "" + return convert_list(element.value,output) + elif element.type == "env_marker": + return f"e{element.env.index}" + elif element.type == "lambda": + return f"λ_{element.control_structure}{element.bounded_variable}" + elif element.type == "delta": + return f"δ_{element.control_structure}" + elif element.type == "gamma": + return "γ" + elif element.type == "beta": + return "β" + elif element.type == "eta": + return f"η_{element.control_structure}[{element.bounded_variable}]" + elif element.type == "tau": + return f"tau[{element.value}]" + else: + return element.value + +def convert_list(element,out): + """Convert a list to a string. + + Args: + element (list): The list to convert. + out (str): The string to append the converted list to. + + Returns: + str: The string with the converted list appended to it. + """ + if isinstance(element, list): + out += "(" + for el in element: + out = convert_list(el,out) + out = out[:-1] + ")" + else: + if isinstance(element.value, list): + out += "(" + for el in element.value: + out = convert_list(el,out) + out = out[:-1] + ")," + else: + out += str(element.value) + "," + return out + +def raw(string): + return string.encode('unicode_escape').decode() \ No newline at end of file diff --git a/doc/About RPAL.pdf b/docs/About RPAL.pdf similarity index 100% rename from doc/About RPAL.pdf rename to docs/About RPAL.pdf diff --git a/doc/ProgrammingProject.pdf b/docs/ProgrammingProject.pdf similarity index 100% rename from doc/ProgrammingProject.pdf rename to docs/ProgrammingProject.pdf diff --git a/doc/RPAL_Grammar.pdf b/docs/RPAL_Grammar.pdf similarity index 100% rename from doc/RPAL_Grammar.pdf rename to docs/RPAL_Grammar.pdf diff --git a/doc/RPAL_Lex.pdf b/docs/RPAL_Lex.pdf similarity index 100% rename from doc/RPAL_Lex.pdf rename to docs/RPAL_Lex.pdf diff --git a/doc/semantics.pdf b/docs/semantics.pdf similarity index 100% rename from doc/semantics.pdf rename to docs/semantics.pdf diff --git a/docs/test_case_pass/ast_test_case_passes.png b/docs/test_case_pass/ast_test_case_passes.png new file mode 100644 index 0000000..b72871b Binary files /dev/null and b/docs/test_case_pass/ast_test_case_passes.png differ diff --git a/docs/test_case_pass/eval_test_cases_passes.png b/docs/test_case_pass/eval_test_cases_passes.png new file mode 100644 index 0000000..414b25a Binary files /dev/null and b/docs/test_case_pass/eval_test_cases_passes.png differ diff --git a/docs/test_case_pass/st_test_cases_passes.png b/docs/test_case_pass/st_test_cases_passes.png new file mode 100644 index 0000000..8a6a870 Binary files /dev/null and b/docs/test_case_pass/st_test_cases_passes.png differ diff --git a/docs/working_switches/ast_switch.png b/docs/working_switches/ast_switch.png new file mode 100644 index 0000000..9e0fa36 Binary files /dev/null and b/docs/working_switches/ast_switch.png differ diff --git a/docs/working_switches/default_switches.png b/docs/working_switches/default_switches.png new file mode 100644 index 0000000..935ea86 Binary files /dev/null and b/docs/working_switches/default_switches.png differ diff --git a/docs/working_switches/st_switch.png b/docs/working_switches/st_switch.png new file mode 100644 index 0000000..caf8094 Binary files /dev/null and b/docs/working_switches/st_switch.png differ diff --git a/errors_handling/cse_error_handler.py b/errors_handling/cse_error_handler.py new file mode 100644 index 0000000..f450eb9 --- /dev/null +++ b/errors_handling/cse_error_handler.py @@ -0,0 +1,22 @@ +# error_handling/error_handler.py + +# This is the main module for error handling. It provides a class ErrorHandler with static methods for handling errors. +class CseErrorHandler: + """ + This class provides a set of static methods for handling errors. + """ + def __init__(self, cse_machine): + """ + This is the constructor for the ErrorHandler class. + """ + self.cse_machine = cse_machine + + def handle_error(self,message): + """ + This method raises an exception with the given message. + + Args: + message (str): The error message. + """ + self.cse_machine._print_cse_table() + raise Exception(message) diff --git a/interpreter/execution_engine.py b/interpreter/execution_engine.py index f18537d..df9a4bf 100644 --- a/interpreter/execution_engine.py +++ b/interpreter/execution_engine.py @@ -17,6 +17,7 @@ from parser.parser_module import Parser from parser.build_standard_tree import StandardTree from cse_machine.machine import CSEMachine +from cse_machine.data_structures.enviroment import Environment import utils.token_printer import utils.tree_list import utils.tree_printer @@ -46,12 +47,16 @@ def __init__(self): self.scanner = Scanner() # Initialize the scanner object self.screener = Screener() # Initialize the screener object self.parser = Parser() # Initialize the parser object - self.standard_tree = StandardTree() # Initialize the standard tree builder object + self.standard_tree = ( + StandardTree() + ) # Initialize the standard tree builder object self.cse_machine = CSEMachine() # Initialize the CSE machine object self.tokens = [] # Initialize a list to store tokens self.filtered_tokens = [] # Initialize a list to store filtered tokens self.parse_ast_tree = None # Initialize the parse ast tree self.parse_st_tree = None # Initialize the parse st tree + self.raw_output = None # Initialize the raw output + self.output = None # Initialize the output def interpret(self, file_name): """ @@ -63,21 +68,34 @@ def interpret(self, file_name): try: # Read content from the file str_content = utils.file_handler.read_file_content(file_name) + # Tokenize the content self.tokens = self.scanner.token_scan(str_content) + # Filter tokens self.filtered_tokens = self.screener.screener(self.tokens) + # Parse the filtered tokens self.parser.parse(self.filtered_tokens.copy()) self.parse_ast_tree = self.parser.stack.peek() + # convert the ast tree to standard tree self.standard_tree.build_standard_tree(self.parse_ast_tree) self.parse_st_tree = self.standard_tree.standard_tree - # self.standard_tree.build_standard_tree(self.parse_ast_tree) - # self.parse_st_tree = self.standard_tree.standard_tree + + # evaluate the standard tree + self.cse_machine.execute(self.parse_st_tree) + + # get the raw output and formatted output + self.raw_output = self.cse_machine._generate_raw_output() + self.output = self.cse_machine._generate_output() + + # Reset the environment index + Environment().reset_index() except FileNotFoundError: print(f"File '{file_name}' not found.") + except Exception as e: print(f"An error occurred in {e}") if self.scanner.status == False: @@ -103,6 +121,12 @@ def print_filtered_tokens(self): else: print("Scanning failed. Filtered tokens cannot be printed.") + def print_cse_table(self): + """ + Print the CSE table. + """ + self.cse_machine._print_cse_table() + def print_AST(self): """ Prints the Abstract Syntax Tree (AST) of the program. @@ -162,3 +186,37 @@ def get_st_list(self): else: # Print a message indicating that AST cannot be printed return [] + def get_raw_output(self): + """ + Retrieves the raw output generated by the CSE machine. + + Returns: + str: The raw output generated by the CSE machine. + """ + return self.raw_output + + def get_output(self): + """ + Retrieves the formatted output generated by the CSE machine. + + Returns: + str: The formatted output generated by the CSE machine. + """ + return self.output + + def print_output(self): + """ + Prints the formatted output generated by the CSE machine. + """ + print(self.output) + + def clean_up(self): + """ + Reset all attributes to their initial state. + """ + self.tokens = [] + self.filtered_tokens = [] + self.parse_ast_tree = None + self.parse_st_tree = None + self.raw_output = None + self.output = None \ No newline at end of file diff --git a/main.py b/myrpal.py similarity index 86% rename from main.py rename to myrpal.py index 590063f..75f42fb 100644 --- a/main.py +++ b/myrpal.py @@ -2,7 +2,7 @@ # This script serves as the main entry point for interpreting RPAL programs. It provides functionality to interpret RPAL code, print Abstract Syntax Trees (ASTs), tokens, and filtered tokens, as well as execute the original RPAL interpreter on a file and print the AST. # Usage: -# python main.py [-ast] [-t] [-ft] [-st] [-r] [-rast] file_name +# python main.py [-ast] [-t] [-ft] [-st] [-r] [-rast] [-ct] file_name # Arguments: # file_name: The name of the RPAL file to interpret. @@ -14,6 +14,7 @@ # -st: Print the Standard Tree for the given RPAL program. (Not yet implemented) # -r: Execute the original RPAL interpreter on the given RPAL program file (file should be in rpal_tests/rpal_source). # -rast: Execute the original RPAL interpreter on the given RPAL program file and print the AST. (file should be in rpal_tests/rpal_source) +# -ct : Print the cse table for the given RPAL program. # Examples: # To interpret an RPAL program: @@ -27,6 +28,7 @@ # -r: python main.py -r file_name # -rast: python main.py -rast file_name # -rst: python main.py -rst file_name +# -CT: python main.py -ct file_name import sys import platform @@ -56,7 +58,7 @@ def main(): # Check if there are enough command-line arguments if len(sys.argv) < 2: print( - "Usage: python main.py [-ast] [-t] [-ft] [-st] [-r] [-rast] file_name ") + "Usage: python main.py [-ast] [-t] [-ft] [-st] [-r] [-rast] [-ct] file_name ") return # Get the filename from the command-line arguments @@ -91,8 +93,7 @@ def main(): try: handle_original_rpal_eval(file_name) except: - print( - "Error in original RPAL evaluation\n(file should be in rpal_test/rpal_source file)") + print("Error in original RPAL evaluation\n(file should be in rpal_test/rpal_source file)") elif sys.argv[1] == "-rast": # Print the original RPAL evaluation(file should be in rpal_test/rpal_source file) try: @@ -105,17 +106,18 @@ def main(): try: handle_original_rpal_st(file_name) except: - print( - "Error in original RPAL evaluation\n(file should be in rpal_test/rpal_source file)") + print("Error in original RPAL evaluation\n(file should be in rpal_test/rpal_source file)") + elif sys.argv[1] == "-ct": + # Print the CSE table + try: + handle_cse_table_option(evaluator) + except: + print("Error in printing CSE table") else: # Default behavior: Evaluate the program - # handle_default_behavior(evaluator) - print("Not yet implemented") - """ l = [] - for program in test_programs: - l.append(rpal_exe(program,True)) - print(l) """ + handle_default_behavior(evaluator) + def handle_ast_option(evaluator): @@ -160,7 +162,7 @@ def handle_default_behavior(evaluator): """ # Your code for default behavior - print("Not yet implemented") + evaluator.print_output() def handle_tokens_option(evaluator): @@ -240,6 +242,20 @@ def handle_original_rpal_st(file_name): else: print("Original RPAL ST generation is not supported on this operating system.") +def handle_cse_table_option(evaluator): + """ + Prints the CSE table for the given file. + + Args: + evaluator (Evaluator): An instance of the Evaluator class. + + Returns: + None + + """ + # Your code to print the CSE table + evaluator.print_cse_table() + if __name__ == "__main__": main() diff --git a/parser/build_standard_tree.py b/parser/build_standard_tree.py index 6b1c3fd..fcd71c0 100644 --- a/parser/build_standard_tree.py +++ b/parser/build_standard_tree.py @@ -50,22 +50,7 @@ def _init_(self): self.error_handler = ErrorHandler() # Operators and unary operators for tree transformation - self.binary_operators = [ - "aug", - "or", - "&", - "+", - "-", - "/", - "", - "gr", - "ge", - "ls", - "le", - "eq", - "ne", - "and", - ] + self.binary_operators = ["aug","or","&","+","-","/","**","gr","ge","ls","le","eq","ne","and"] self.unary_operators = ["not", "neg"] # Placeholder for the standard tree diff --git a/requirements.txt b/requirements.txt index ee5c952..4f5b154 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ pytest==8.1.1 +pytest-timeout diff --git a/rpal_tests/assert_program.py b/rpal_tests/assert_program.py index c1dd859..48590c6 100644 --- a/rpal_tests/assert_program.py +++ b/rpal_tests/assert_program.py @@ -31,7 +31,7 @@ def program(source_file_name,flag=None): # Execution: Obtain actual output by interpreting the program using the Evaluator evaluator = Evaluator() - actual_output = evaluator.interpret(source_file_path) + evaluator.interpret(source_file_path) if flag == "ast": actual_output = "\n".join(evaluator.get_ast_list()) @@ -43,7 +43,7 @@ def program(source_file_name,flag=None): print("\nactual output :\n",actual_output, "\n") else: # Manually set the actual output for testing purposes - actual_output = "15\n" + actual_output = evaluator.get_output() print("\nactual output :\n",actual_output,"raw version",repr(actual_output), "\n") # Assertion: Compare original output with actual output diff --git a/rpal_tests/pytest.ini b/rpal_tests/pytest.ini new file mode 100644 index 0000000..be78810 --- /dev/null +++ b/rpal_tests/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +timeout = 3 +timeout_method = thread diff --git a/rpal_tests/rpal_sources/tiny b/rpal_tests/rpal_sources/tiny index baca898..fa73437 100644 --- a/rpal_tests/rpal_sources/tiny +++ b/rpal_tests/rpal_sources/tiny @@ -98,7 +98,7 @@ in Print ( PP ('program', (';', - (':=', 'x',3), + (':=', 'x',5), ('print', 'x') ) ) (nil aug 3) diff --git a/rpal_tests/rpal_sources/wsum1 b/rpal_tests/rpal_sources/wsum1 index 98fc9ad..dab4b7d 100644 --- a/rpal_tests/rpal_sources/wsum1 +++ b/rpal_tests/rpal_sources/wsum1 @@ -14,7 +14,7 @@ let WS IS = PWS IS 1 0 | x + y in Print ( -// WS (nil aug 2), +// WS (nil aug 2) WS (1,(1,(nil,nil),2),3) // WS 1 // WS nil diff --git a/rpal_tests/test_generate_ast_tests.py b/rpal_tests/test_generate_ast_tests.py index 34c8be6..a823431 100644 --- a/rpal_tests/test_generate_ast_tests.py +++ b/rpal_tests/test_generate_ast_tests.py @@ -43,7 +43,7 @@ def test_program(program_name): # Execution: Obtain actual output by interpreting the program using the Evaluator evaluator = Evaluator() - actual_output = evaluator.interpret(source_file_path) + evaluator.interpret(source_file_path) actual_output = "\n".join(evaluator.get_ast_list()) actual_output += ("\n"+out[test_programs.index(program_name)]) expected_output = out_ast[test_programs.index(program_name)] diff --git a/rpal_tests/test_generate_st_tests.py b/rpal_tests/test_generate_st_tests.py index fe5c8d7..87b2614 100644 --- a/rpal_tests/test_generate_st_tests.py +++ b/rpal_tests/test_generate_st_tests.py @@ -43,7 +43,7 @@ def test_program(program_name): # Execution: Obtain actual output by interpreting the program using the Evaluator evaluator = Evaluator() - actual_output = evaluator.interpret(source_file_path) + evaluator.interpret(source_file_path) actual_output = "\n".join(evaluator.get_st_list()) actual_output += ("\n"+out[test_programs.index(program_name)]) expected_output = out_st[test_programs.index(program_name)] diff --git a/rpal_tests/test_generate_tests.py b/rpal_tests/test_generate_tests.py index fec2cb4..1725e54 100644 --- a/rpal_tests/test_generate_tests.py +++ b/rpal_tests/test_generate_tests.py @@ -26,9 +26,10 @@ # Parametrize the test cases dynamically test_cases = list(zip(test_programs, out)) +@pytest.mark.timeout(1) @pytest.mark.parametrize("program_name", test_programs) def test_program(program_name): - """ # Get the current directory + # Get the current directory current_directory = os.path.dirname(os.path.abspath(__file__)) # Construct the full path to the source file @@ -41,8 +42,8 @@ def test_program(program_name): # Execution: Obtain actual output by interpreting the program using the Evaluator evaluator = Evaluator() - actual_output = evaluator.interpret(source_file_path) """ - actual_output = "15\n" + evaluator.interpret(source_file_path) + actual_output = evaluator.get_output() expected_output = out[test_programs.index(program_name)] assert actual_output == expected_output diff --git a/rpal_tests/test_generate_tests_with_rpal_exe.py b/rpal_tests/test_generate_tests_with_rpal_exe.py index d3ffae7..46d875e 100644 --- a/rpal_tests/test_generate_tests_with_rpal_exe.py +++ b/rpal_tests/test_generate_tests_with_rpal_exe.py @@ -7,22 +7,17 @@ # The test_program function can be used to generate test cases for the RPAL interpreter by comparing the output of the RPAL program obtained by running the program using rpal_exe with the output obtained by interpreting the program using the Evaluator. import pytest +import time from rpal_tests.assert_program import program from rpal_tests.program_name_list import test_programs -""" -This function is a test function that is used to test the functionality of the interpreter. - -Args: - program_name (str): The name of the program to be tested. - -Returns: - tuple: A tuple containing the actual and original program strings. - -""" +# Fixture to add a delay between test cases +@pytest.fixture(scope="function", autouse=True) +def delay_between_test_cases(): + # Wait for 1 second between test cases + time.sleep(0) # Parametrize the test cases dynamically - @pytest.mark.parametrize("program_name", test_programs) def test_program(program_name): actual_program ,original_program = program(program_name) @@ -30,4 +25,5 @@ def test_program(program_name): + \ No newline at end of file diff --git a/utils/control_structure_element.py b/utils/control_structure_element.py new file mode 100644 index 0000000..da647d3 --- /dev/null +++ b/utils/control_structure_element.py @@ -0,0 +1,8 @@ +class ControlStructureElement: + def __init__(self, type, value, bounded_variable=None,control_structure=None, env=None , operator=None): + self.type = type + self.value = value + self.bounded_variable = bounded_variable + self.control_structure = control_structure + self.env = env + self.operator = operator diff --git a/utils/node.py b/utils/node.py index f8c33b2..bcdf6a5 100644 --- a/utils/node.py +++ b/utils/node.py @@ -12,7 +12,7 @@ class Node: A node contains a data field and a list of child nodes. """ - def __init__(self, data): + def __init__(self, data ): """ Initialize a new node with the given data. diff --git a/utils/stack.py b/utils/stack.py index 3f728be..f14107a 100644 --- a/utils/stack.py +++ b/utils/stack.py @@ -50,4 +50,10 @@ def size(self): Return the number of items in the stack. """ return len(self.items) + + def whole_stack(self): + """ + Return the whole stack. + """ + return self.items