fancycalculator / app.py
KamalShahid's picture
Update app.py
4100dd9 verified
import math, time
from typing import List, Tuple
import gradio as gr
import numpy as np
HISTORY: List[Tuple[str, str]] = []
def safe_eval(expr: str):
"""
Evaluate math expressions safely by exposing only math & numpy.
Supports +,-,*,/,**,%, parentheses, and functions like sin, cos, tan, log, sqrt, pi, e.
"""
if not expr or expr.strip() == "":
return ""
allowed = {
"math": math, "np": np,
# common names directly:
"sin": math.sin, "cos": math.cos, "tan": math.tan,
"asin": math.asin, "acos": math.acos, "atan": math.atan,
"sqrt": math.sqrt, "log": math.log, "log10": math.log10, "exp": math.exp,
"pow": pow, "pi": math.pi, "e": math.e, "abs": abs, "round": round
}
# never expose __builtins__ or globals
return eval(expr, {"__builtins__": {}}, allowed)
def calculate(expr, hist):
try:
result = safe_eval(expr)
stamp = time.strftime("%H:%M:%S")
row = (f"{stamp} {expr}", str(result))
HISTORY.insert(0, row)
if len(HISTORY) > 25:
HISTORY.pop()
return str(result), HISTORY
except Exception as e:
return f"Error: {e}", HISTORY
def scientific(button, value, hist):
piece = button
# map some labeled buttons to functions/tokens
mapping = {
"π":"pi", "e":"e", "√":"sqrt(", "ln":"log(", "log10":"log10(", "sin":"sin(", "cos":"cos(", "tan":"tan(",
"^":"**"
}
piece = mapping.get(button, button)
new_value = (value or "") + piece
return new_value
def clear_all():
HISTORY.clear()
return "", []
def matrix_op(a, b, op):
try:
A = np.array(a)
B = np.array(b)
if op == "A + B":
out = A + B
elif op == "A - B":
out = A - B
elif op == "A × B":
out = A @ B
else:
raise ValueError("Unknown op")
return out.tolist()
except Exception as e:
return f"Error: {e}"
def convert_units(value, kind):
try:
x = float(value)
if kind == "Length (m ↔ ft)":
# round-trip toggle: show both
return f"{x} m = {x*3.28084:.6f} ft | {x} ft = {x/3.28084:.6f} m"
if kind == "Weight (kg ↔ lb)":
return f"{x} kg = {x*2.20462:.6f} lb | {x} lb = {x/2.20462:.6f} kg"
if kind == "Temperature (°C ↔ °F)":
c2f = x*9/5 + 32
f2c = (x-32)*5/9
return f"{x} °C = {c2f:.2f} °F | {x} °F = {f2c:.2f} °C"
return "Unsupported kind"
except Exception as e:
return f"Error: {e}"
theme = gr.themes.Soft(primary_hue="violet", neutral_hue="slate")
with gr.Blocks(title="Fancy Calculator", theme=theme, css="""
.kbd {border:1px solid var(--border-color-primary); padding:2px 6px; border-radius:6px;}
.history td {padding: 4px 8px;}
""") as demo:
gr.Markdown("# 🧮 Fancy Calculator\n**Basic, Scientific, Matrix, and Unit tools — with history**")
with gr.Tab("Basic / Scientific"):
with gr.Row():
expr = gr.Textbox(label="Expression", placeholder="e.g., (2+3)*5, sin(pi/6), sqrt(2)**2", autofocus=True)
with gr.Row():
out = gr.Textbox(label="Result", interactive=False)
with gr.Row():
buttons = ["7","8","9","+","sin","cos","tan","(",
"4","5","6","-","√","ln","log10",")",
"1","2","3","*","^","π","e","/",
"0",".","%","//","**","=","C","Back"]
grid = []
for b in buttons:
grid.append(gr.Button(b, variant="secondary" if b not in ["=","C"] else "primary"))
hist = gr.Dataframe(headers=["Expression","Result"], datatype=["str","str"],
value=HISTORY, interactive=False, row_count=(0,"dynamic"),
label="History (latest first)", elem_classes=["history"])
# wiring
for btn in grid:
if btn.value == "=":
btn.click(calculate, [expr, hist], [out, hist])
elif btn.value == "C":
btn.click(lambda: ("", []), None, [expr, hist]).then(fn=clear_all, outputs=[expr, hist])
elif btn.value == "Back":
btn.click(lambda s: s[:-1] if s else "", expr, expr)
else:
btn.click(scientific, [btn, expr, hist], expr)
# pressing Enter in the textbox calculates
expr.submit(calculate, [expr, hist], [out, hist])
gr.Markdown(
"Tips: Use functions like `sin, cos, tan, sqrt, log, log10, exp, pi, e`. "
"Keyboard works too — hit **Enter** to evaluate. "
"Use `**` for power, `//` for integer division."
)
with gr.Tab("Matrix"):
gr.Markdown("Enter matrices as nested lists, e.g. `[[1,2],[3,4]]`")
A = gr.Textbox(value="[[1,2],[3,4]]", label="Matrix A")
B = gr.Textbox(value="[[5,6],[7,8]]", label="Matrix B")
op = gr.Radio(["A + B","A - B","A × B"], value="A + B", label="Operation")
mat_out = gr.Textbox(label="Output")
run = gr.Button("Compute", variant="primary")
run.click(lambda a,b,o: matrix_op(eval(a), eval(b), o), [A,B,op], mat_out)
with gr.Tab("Unit Converter"):
kind = gr.Dropdown(["Length (m ↔ ft)","Weight (kg ↔ lb)","Temperature (°C ↔ °F)"],
value="Length (m ↔ ft)", label="Conversion")
val = gr.Textbox(value="1", label="Value")
conv_btn = gr.Button("Convert", variant="primary")
conv_out = gr.Textbox(label="Result")
conv_btn.click(convert_units, [val, kind], conv_out)
gr.Markdown(
"Built with ❤️ using [Gradio](https://www.gradio.app/)."
)
if __name__ == "__main__":
demo.launch()