Skip to main content

Interactive Calculator CLI

This is not a toy calculator.

This project is designed to force you to build a small, reliable system that lives at Python’s most important boundary:

Human input.

You will practice:

  • Robust input and output
  • Type casting and coercion rules
  • Operator semantics and precedence
  • Truthiness and validation patterns
  • Defensive engineering (no crashes on bad input)
  • Safe evaluation without using eval

By the end, you will have a CLI tool that behaves like a real product.

Concept Overview

You are building a loop-driven CLI application that:
1) reads user commands
2) validates and parses them
3) executes operations safely
4) prints results in a predictable format
5) handles errors without crashing

This is exactly what production CLIs do.

Non-Negotiable Rule

You are not allowed to use:

eval(...)

Because:

  • It executes arbitrary code
  • It is unsafe
  • It teaches the wrong habits
  • It bypasses real parsing and operator reasoning

You will build a safe evaluator instead.

What You Will Build

Your calculator will support:

  • Basic operations: add, sub, mul, div
  • Extended operations: floor division, modulo, power
  • Unary operations: negation, absolute
  • Memory register: store and recall a value
  • History: list last N computations
  • Exit commands: exit, quit

Input examples:

add 10 20
sub 100 45
mul 7 8
div 10 4
floordiv 10 4
mod 10 4
pow 2 10
abs -42
neg 15

Engineering Outcomes (Why This Project Exists)

After this project, you should be able to explain:

  1. Why input is always a string and must be parsed
  2. Why casting can fail and must be defended
  3. Why division returns float and floor division returns int
  4. Why truthiness can break validation if done incorrectly
  5. Why operators are not just symbols but runtime mechanics
  6. Why safe evaluation matters in production

Project Phases

You will build this in steps.

Do not skip steps.

Each step exists to teach a runtime concept.

Step 1 - Skeleton CLI Loop (No Math Yet)

Goal: Create a CLI that continuously reads commands until user exits.

Requirements:

  • Print a banner on start
  • Show a prompt each loop
  • Exit on "exit" or "quit"
  • Ignore empty input safely

Implementation outline:

def main():
print("Calculator CLI. Type 'help' for commands. Type 'exit' to quit.")
while True:
raw = input("calc> ").strip()
if not raw:
continue
if raw in ("exit", "quit"):
print("Bye.")
break

Test Cases:

  • Input empty line should not crash
  • "exit" should stop
  • "quit" should stop

Reflection:

  • Why does input return str?
  • Why should you strip whitespace?

Step 2 - Tokenization and Command Parsing

Goal: Convert input text into tokens.

Example:

Input:

add 10 20

Tokens:

["add", "10", "20"]

Rules:

  • Split on whitespace
  • First token is command
  • Rest are arguments

Implementation:

tokens = raw.split()
cmd = tokens[0].lower()
args = tokens[1:]

Test Cases:

  • Extra spaces between words
  • Mixed case commands

Reflection:

  • Why should commands be lowercased?
  • What happens if user types only "add"?

Step 3 - Type Conversion Layer (The Parser)

Goal: Convert args to numbers safely.

Your parser should support:

  • int
  • float

Rules:

  • Try int first if it looks like integer
  • Otherwise try float
  • Reject invalid values

Implementation idea:

def parse_number(token: str):
try:
if token.isdigit() or (token.startswith("-") and token[1:].isdigit()):
return int(token)
return float(token)
except ValueError:
raise ValueError(f"Invalid number: {token}")

Test Cases:

  • "10"
  • "-10"
  • "3.14"
  • "-0.5"
  • "abc" should error safely

Reflection:

  • Why does int("10.5") fail?
  • Why is float precision dangerous?

Step 4 - Operator Execution Layer

Goal: Create a dispatch table from command name to function.

Example mapping:

OPS = {
"add": lambda a, b: a + b,
"sub": lambda a, b: a - b,
"mul": lambda a, b: a * b,
"div": lambda a, b: a / b,
}

Then:

  • Validate correct number of args
  • Parse args
  • Execute
  • Print result

Core execution:

func = OPS[cmd]
a = parse_number(args[0])
b = parse_number(args[1])
result = func(a, b)
print(result)

Test Cases:

  • div 10 4 returns float
  • div 10 0 handled safely

Reflection:

  • Why does / always return float?
  • Why does division by zero raise error?

Step 5 - Error Handling as Product Behavior

Goal: Never crash on bad input.

All errors should:

  • Show a clean error message
  • Continue loop

Pattern:

try:
...
except Exception as e:
print(f"Error: {e}")
continue

But do not hide bugs silently.

For production:

  • catch expected errors
  • let unexpected errors surface in development

Test Cases:

  • Unknown command
  • Missing args
  • Invalid numbers

Reflection:

  • Why is swallowing all exceptions dangerous?
  • Which exceptions should be handled?

Step 6 - Add Help Command

Implement:

help

Should show:

  • All supported commands
  • Usage patterns
  • Examples

Make help output readable and consistent.

Step 7 - Add Memory Register

Add:

  • store (stores last result)
  • recall (prints stored value)
  • clear (clears memory)

Design:

  • memory starts as None
  • recall errors if memory is None

Example:

add 5 5
store
recall

Reflection:

  • Why is None best sentinel here?
  • Why should you check identity using is None?

Step 8 - Add History

Store last N calculations.

Requirements:

  • history command prints latest entries
  • store expression and result
  • limit length to N (like 20)

Data model:

history = []
history.append(("add 1 2", 3))

Reflection:

  • Why should history store raw string and parsed numbers?
  • What is the trade-off?

Safe Evaluation Challenge (Optional Advanced)

Support expressions like:

2 + 3 * 4

Without eval.

Approach:

  • Tokenize numbers and operators
  • Implement precedence:
    • power
    • multiply and divide
    • add and subtract
  • Evaluate safely

This is a small expression parser.

Not required, but extremely valuable.

Output Format Standard

Results should be printed as:

  • integers as integers
  • floats with limited precision

Example:

result: 2
result: 2.5

Avoid printing huge float artifacts.

Use rounding when necessary.

Engineering Checklist

Your final calculator must:

  • Never crash on bad input
  • Never use eval
  • Validate arg counts per command
  • Handle division by zero cleanly
  • Support help, history, memory
  • Be readable and modular
  • Keep parsing separate from execution

Stretch Extensions

  1. Add config for history size
  2. Add support for parentheses in expression parsing
  3. Add scientific functions: sqrt, sin, cos
  4. Add unit tests for parser and ops
  5. Add a "mode" for int-only operations

Reflection Questions

  1. Where did type casting cause bugs during development?
  2. Which validation rules saved you from crashes?
  3. Which operators produced surprising type outputs?
  4. How did truthiness affect your input validation?
  5. Why is building a safe evaluator a security lesson?

Your Motto Check

If you can explain every surprising behavior, you are learning Python.

If you cannot explain it, the project is working.

© 2026 EngineersOfAI. All rights reserved.