calc / app.py
prathamt's picture
Update app.py
f78beac verified
Raw
History Blame Contribute Delete
7.78 kB
import ast
import operator as op
import re
import gradio as gr
# ----------------------------
# Safe arithmetic evaluator
# ----------------------------
ALLOWED_OPERATORS = {
ast.Add: op.add,
ast.Sub: op.sub,
ast.Mult: op.mul,
ast.Div: op.truediv,
ast.Mod: op.mod,
ast.Pow: op.pow,
ast.UAdd: op.pos,
ast.USub: op.neg,
}
def _safe_eval_node(node):
if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
return node.value
if isinstance(node, ast.BinOp) and type(node.op) in ALLOWED_OPERATORS:
return ALLOWED_OPERATORS[type(node.op)](
_safe_eval_node(node.left),
_safe_eval_node(node.right),
)
if isinstance(node, ast.UnaryOp) and type(node.op) in ALLOWED_OPERATORS:
return ALLOWED_OPERATORS[type(node.op)](_safe_eval_node(node.operand))
raise ValueError("Unsupported expression")
def safe_eval(expr: str):
expr = expr.replace("×", "*").replace("÷", "/").replace("−", "-")
tree = ast.parse(expr, mode="eval")
return _safe_eval_node(tree.body)
def format_result(value):
if isinstance(value, float) and value.is_integer():
return str(int(value))
return str(value)
# ----------------------------
# Calculator actions
# ----------------------------
def append_value(current, value):
if current in ("0", "Error"):
current = ""
if value in "+-*/":
if not current and value != "-":
return "0", "0"
if current.endswith(("+", "-", "*", "/", ".")):
current = current[:-1]
return current + value, current + value
def clear_display():
return "0", "0"
def backspace(current):
if current in ("0", "Error", ""):
return "0", "0"
new_value = current[:-1]
if not new_value:
new_value = "0"
return new_value, new_value
def evaluate(current):
try:
result = safe_eval(current)
result = format_result(result)
return result, result
except:
return "Error", "Error"
def toggle_sign(current):
if current in ("0", "Error", ""):
return "-", "-"
m = re.search(r"(-?\d+(?:\.\d+)?)$", current)
if not m:
return current, current
start, end = m.span()
num = m.group(0)
if num.startswith("-"):
num = num[1:]
else:
num = "-" + num
new_value = current[:start] + num
return new_value, new_value
def percent(current):
if current in ("0", "Error", ""):
return "0", "0"
if current.endswith((".", "+", "-", "*", "/")):
return current, current
new_value = current + "/100"
return new_value, new_value
def parens(current):
if current in ("0", "Error"):
current = ""
open_count = current.count("(")
close_count = current.count(")")
if not current or current.endswith(("+", "-", "*", "/", "(")):
ch = "("
elif open_count > close_count:
ch = ")"
else:
ch = "("
new_value = current + ch
return new_value, new_value
def make_append(v):
def fn(current):
return append_value(current, v)
return fn
# ----------------------------
# UI
# ----------------------------
CSS = """
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@300;400;600&display=swap');
*{font-family: 'Orbitron', sans-serif !important;}
body, .gradio-container {
background: #000 !important;
color: white !important;
}
.phone {
max-width: 390px;
margin: 0 auto;
padding: 16px 14px 20px;
}
.display textarea {
background: #000 !important;
color: #fff !important;
border: none !important;
box-shadow: none !important;
text-align: right !important;
font-size: 72px !important;
font-weight: 300 !important;
height: 170px !important;
padding: 10px 0 0 0 !important;
resize: none !important;
}
.divider {
height: 1px;
background: rgba(255,255,255,0.18);
margin: 10px 0 18px 0;
}
.topbar {
display: flex;
justify-content: space-between;
align-items: center;
color: rgba(255,255,255,0.8);
font-size: 18px;
margin-bottom: 8px;
opacity: 0.9;
}
button {
height: 82px !important;
min-height: 82px !important;
border-radius: 999px !important;
font-size: 30px !important;
font-weight: 500 !important;
border: none !important;
box-shadow: none !important;
}
.digit button {
background: #272727 !important;
color: #fff !important;
}
.utility button {
background: #a5a5a5 !important;
color: #000 !important;
}
.operator button {
background: #bfbfbf !important;
color: #000 !important;
}
.equals button {
background: #34c759 !important;
color: #fff !important;
}
.zero button {
border-radius: 999px !important;
}
"""
with gr.Blocks(css=CSS, title="Calculator") as demo:
with gr.Column(elem_classes="phone"):
display = gr.Textbox(
value="0",
show_label=False,
interactive=False,
container=False,
lines=1,
elem_classes="display",
)
state = gr.State("0")
gr.HTML("""
<div class="divider"></div>
""")
# Row 1
with gr.Row():
with gr.Column(scale=1, min_width=0):
gr.Button("C", elem_classes=["utility"]).click(clear_display, None, [state, display])
with gr.Column(scale=1, min_width=0):
gr.Button("()", elem_classes=["utility"]).click(parens, state, [state, display])
with gr.Column(scale=1, min_width=0):
gr.Button("%", elem_classes=["utility"]).click(percent, state, [state, display])
with gr.Column(scale=1, min_width=0):
gr.Button("÷", elem_classes=["operator"]).click(make_append("/"), state, [state, display])
# Row 2
with gr.Row():
for label in ["7", "8", "9"]:
with gr.Column(scale=1, min_width=0):
gr.Button(label, elem_classes=["digit"]).click(make_append(label), state, [state, display])
with gr.Column(scale=1, min_width=0):
gr.Button("×", elem_classes=["operator"]).click(make_append("*"), state, [state, display])
# Row 3
with gr.Row():
for label in ["4", "5", "6"]:
with gr.Column(scale=1, min_width=0):
gr.Button(label, elem_classes=["digit"]).click(make_append(label), state, [state, display])
with gr.Column(scale=1, min_width=0):
gr.Button("−", elem_classes=["operator"]).click(make_append("-"), state, [state, display])
# Row 4
with gr.Row():
for label in ["1", "2", "3"]:
with gr.Column(scale=1, min_width=0):
gr.Button(label, elem_classes=["digit"]).click(make_append(label), state, [state, display])
with gr.Column(scale=1, min_width=0):
gr.Button("+", elem_classes=["operator"]).click(make_append("+"), state, [state, display])
# Row 5
with gr.Row():
with gr.Column(scale=1, min_width=0):
gr.Button("+/-", elem_classes=["utility"]).click(toggle_sign, state, [state, display])
with gr.Column(scale=2, min_width=0):
gr.Button("0", elem_classes=["digit", "zero"]).click(make_append("0"), state, [state, display])
with gr.Column(scale=1, min_width=0):
gr.Button(".", elem_classes=["digit"]).click(make_append("."), state, [state, display])
with gr.Column(scale=1, min_width=0):
gr.Button("=", elem_classes=["equals"]).click(evaluate, state, [state, display])
demo.launch(server_name="0.0.0.0", server_port=7860)