Computer Programming 2 - Task 1

Building a Safe GUI Calculator with a History Panel

Introduction & Objectives

Welcome to Lab Task 1 for Computer Programming 2! In this lab, you will set up a virtual environment, install a GUI library, and build a calculator that deliberately avoids eval(). Instead, you will implement a safe expression evaluator. You will also add a History panel that records the last five calculations.

By the end of this lab, you will be able to:

Materials Required

Lab Procedure

Follow these steps carefully.

Learning Outcome 1: The setup steps below address your ability to analyse an engineering application to facilitate the development of a software solution using RAD techniques. By identifying the required library and documenting it in requirements.txt, you perform an analysis that establishes the software requirements for this application.

  1. Step 1: Create a Project Directory

    Create a new directory called safe-calculator and navigate into it.

    mkdir safe-calculator
    cd safe-calculator
  2. Step 2: Create the Virtual Environment

    Create a virtual environment named venv inside your project directory.

    python -m venv venv
    Note: On some systems you may need to use python3 instead of python.
  3. Step 3: Activate the Virtual Environment

    On Windows:

    venv\Scripts\activate

    On macOS / Linux:

    source venv/bin/activate

    The (venv) prefix will appear in your prompt once activated.

  4. Step 4: Install FreeSimpleGUI

    Install the GUI library using pip. Because the virtual environment is active, this installs only into your project environment.

    pip install FreeSimpleGUI
  5. Step 5: Create a requirements.txt File

    Record your project's dependencies with the following command:

    pip freeze > requirements.txt

    Open the file — you should see FreeSimpleGUI and its version number listed.

  6. Learning Outcome 2: The following step addresses your ability to develop a software solution for an engineering application using RAD techniques. You will use FreeSimpleGUI components to build the calculator UI and implement a safe expression parser, demonstrating effective and efficient use of the library's RAD capabilities.

  7. Step 6: Write the Calculator Code

    Create a file named calculator_app.py inside your safe-calculator directory. The calculator must meet the following requirements:

    • Standard digit and operator buttons (0–9, +, -, *, /, ., C, =).
    • An Input display widget showing the current expression.
    • A Multiline widget labelled "History" that shows the last 5 expressions and their results.
    • The computation on = must use a safe evaluator — not eval(). You may use one of the approaches below.
    Security requirement: Using eval() (or exec()) anywhere in your solution will result in a significant mark deduction. Choose one of the safe alternatives described below.

    Recommended safe approach — operator dispatch:

    import operator as op
    
    # Map operator symbols to functions
    OPS = {
        '+': op.add,
        '-': op.sub,
        '*': op.mul,
        '/': op.truediv,
    }
    
    def safe_evaluate(expression):
        """
        Evaluate a simple two-operand expression such as '12.5 + 7'.
        Returns the numeric result, or raises ValueError for invalid input.
        Only handles a single binary operation (no nested parentheses).
        """
        for symbol in ('+', '-', '*', '/'):
            if symbol in expression:
                parts = expression.split(symbol, 1)
                if len(parts) == 2:
                    left = float(parts[0].strip())
                    right = float(parts[1].strip())
                    if symbol == '/' and right == 0:
                        raise ZeroDivisionError("Division by zero")
                    return OPS[symbol](left, right)
        # If no operator found, try to return a plain number
        return float(expression.strip())
    

    Alternative approach — ast module: You may instead use Python's ast module to parse the expression tree and evaluate it without executing arbitrary code. Research ast.parse() and ast.literal_eval() if you choose this route.

    Below is the starter structure for the full application. Complete the # TODO sections:

    import FreeSimpleGUI as sg
    import operator as op
    
    # Safe operator dispatch (or replace with your ast-based approach)
    OPS = {'+': op.add, '-': op.sub, '*': op.mul, '/': op.truediv}
    
    def safe_evaluate(expression):
        """Evaluate a simple binary arithmetic expression without using eval()."""
        # TODO: implement safe evaluation logic (see example above)
        pass
    
    sg.theme('DarkTeal9')
    
    # History stores the last 5 calculation strings
    history = []
    
    layout = [
        # Display row
        [sg.Input(size=(30, 1), font=('Helvetica', 18), justification='right',
                  key='-DISPLAY-', readonly=True)],
        # Button rows
        [sg.Button('C', size=(4,2)), sg.Button('(', size=(4,2)),
         sg.Button(')', size=(4,2)), sg.Button('/', size=(4,2))],
        [sg.Button('7', size=(4,2)), sg.Button('8', size=(4,2)),
         sg.Button('9', size=(4,2)), sg.Button('*', size=(4,2))],
        [sg.Button('4', size=(4,2)), sg.Button('5', size=(4,2)),
         sg.Button('6', size=(4,2)), sg.Button('-', size=(4,2))],
        [sg.Button('1', size=(4,2)), sg.Button('2', size=(4,2)),
         sg.Button('3', size=(4,2)), sg.Button('+', size=(4,2))],
        [sg.Button('0', size=(9,2)), sg.Button('.', size=(4,2)),
         sg.Button('=', size=(4,2))],
        # History panel
        [sg.Text('History:')],
        [sg.Multiline('', size=(30, 5), key='-HISTORY-', disabled=True,
                      font=('Courier New', 10))],
    ]
    
    window = sg.Window('Safe Calculator', layout)
    current_expression = ''
    
    while True:
        event, values = window.read()
        if event == sg.WIN_CLOSED:
            break
    
        if event == 'C':
            current_expression = ''
            window['-DISPLAY-'].update('0')
    
        elif event == '=':
            try:
                result = safe_evaluate(current_expression)
                result_str = str(result)
                # TODO: add "expression = result" to history list (keep last 5)
                window['-DISPLAY-'].update(result_str)
                window['-HISTORY-'].update('\n'.join(history))
                current_expression = result_str
            except ZeroDivisionError:
                window['-DISPLAY-'].update('Div/0 Error')
                current_expression = ''
            except Exception:
                window['-DISPLAY-'].update('Error')
                current_expression = ''
    
        else:
            current_expression += event
            window['-DISPLAY-'].update(current_expression)
    
    window.close()
    
  8. Step 7: Run Your Application

    Save calculator_app.py and run it with your virtual environment active:

    python calculator_app.py

    Test the calculator. Verify that the History panel updates after each calculation and that all four arithmetic operations work.

  9. Step 8: Deactivate the Virtual Environment

    deactivate

Evaluation and Reflection

Learning Outcome 3: This section addresses your ability to evaluate the performance of a RAD software solution to an engineering application. You must technically appraise the performance of your application, use informed judgement to identify deficiencies, and propose improvements.

Answer the following questions in a separate document (PDF or .txt). Your answers should be concise but thorough.

  1. Appraise Security: Your safe evaluator rejects arbitrary input like __import__('os').system('ls'). Run this string through your safe_evaluate() function (test it in a Python shell, not in the GUI) and describe what happens. Does your evaluator raise an exception, return an error, or behave unexpectedly? Explain why your approach is safer than eval() for this input.
  2. Compare Approaches: Compare the eval()-based calculator from the original lab with your safe-parser implementation. Discuss the security trade-off and the maintainability trade-off — in what situations might each approach be acceptable?
  3. Identify Areas for Improvement: Your current safe evaluator handles only a single binary operation. Propose one concrete enhancement — for example, support for operator precedence, parentheses, or unary negation. Describe the logic of your proposed enhancement without writing the code.

Lab Submission

Submit the following files:

Troubleshooting

Problem: ModuleNotFoundError: No module named 'FreeSimpleGUI'

Solution: Your virtual environment is not active. Run the activation command from Step 3 again and look for the (venv) prefix before re-running the script.

Problem: The calculator shows Error for expressions like 3+4*2.

Solution: The operator-dispatch approach above only handles a single operator. Split the expression on the first operator found. If you want to support compound expressions, look into the shunting-yard algorithm or the ast module.