Started sls_calc app

This commit is contained in:
Kyler Olsen 2025-12-04 22:12:48 -07:00
parent d8e38ada85
commit 7b57807fc7
2 changed files with 305 additions and 0 deletions

View File

View File

@ -0,0 +1,305 @@
#!/usr/bin/env python3
"""
HP-Style RPN Calculator using Stack Language Backend
Implements classic HP calculator interface with stack display
"""
import tkinter as tk
from tkinter import ttk, font as tkfont
import sls_py
class HPCalculator:
def __init__(self, root):
self.root = root
self.root.title("HP Stack Calculator")
self.root.geometry("450x650")
self.root.resizable(False, False)
# Initialize interpreter
self.interp = sls_py.InterpreterState()
self.lexer = sls_py.LexerInfo()
# Current entry buffer
self.entry_buffer = ""
self.is_new_entry = True
# Set up UI
self.setup_styles()
self.create_widgets()
def setup_styles(self):
"""Configure custom styles for the calculator"""
style = ttk.Style()
style.theme_use('clam')
# Configure button styles
style.configure('Calc.TButton',
font=('Arial', 12, 'bold'),
padding=10)
style.configure('Display.TLabel',
font=('Courier', 14),
background='#f0f0f0',
relief='sunken',
padding=5)
def create_widgets(self):
"""Create all calculator widgets"""
# Main container
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Stack display (shows 4 stack levels like classic HP)
self.create_stack_display(main_frame)
# Entry display
self.create_entry_display(main_frame)
# Button grid
self.create_button_grid(main_frame)
def create_stack_display(self, parent):
"""Create the 4-level stack display"""
stack_frame = ttk.LabelFrame(parent, text="Stack", padding="5")
stack_frame.grid(row=0, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(0, 10))
self.stack_labels = []
for i in range(4):
label = ttk.Label(stack_frame,
text=f"T{3-i}: 0",
style='Display.TLabel',
anchor='e',
width=30)
label.grid(row=i, column=0, sticky=(tk.W, tk.E), pady=2)
self.stack_labels.append(label)
def create_entry_display(self, parent):
"""Create the current entry display"""
entry_frame = ttk.Frame(parent)
entry_frame.grid(row=1, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(0, 10))
self.entry_label = ttk.Label(entry_frame,
text="0",
style='Display.TLabel',
anchor='e',
font=('Courier', 16, 'bold'))
self.entry_label.grid(row=0, column=0, sticky=(tk.W, tk.E))
def create_button_grid(self, parent):
"""Create the calculator button grid"""
buttons = [
# Row 1: Stack operations
[('', self.sqrt), ('', self.square), ('1/x', self.reciprocal), ('÷', self.divide)],
# Row 2: Numbers and operations
[('7', lambda: self.digit('7')), ('8', lambda: self.digit('8')),
('9', lambda: self.digit('9')), ('×', self.multiply)],
# Row 3
[('4', lambda: self.digit('4')), ('5', lambda: self.digit('5')),
('6', lambda: self.digit('6')), ('', self.subtract)],
# Row 4
[('1', lambda: self.digit('1')), ('2', lambda: self.digit('2')),
('3', lambda: self.digit('3')), ('+', self.add)],
# Row 5
[('0', lambda: self.digit('0')), ('.', self.decimal),
('±', self.negate), ('ENTER', self.enter)],
# Row 6: Special functions
[('SWAP', self.swap), ('DROP', self.drop),
('DUP', self.dup), ('CLR', self.clear)]
]
for row_idx, row in enumerate(buttons, start=2):
for col_idx, (text, command) in enumerate(row):
btn = ttk.Button(parent,
text=text,
command=command,
style='Calc.TButton',
width=8)
btn.grid(row=row_idx, column=col_idx, padx=2, pady=2, sticky=(tk.W, tk.E, tk.N, tk.S))
# Keyboard bindings
self.root.bind('<Return>', lambda e: self.enter())
self.root.bind('<KP_Enter>', lambda e: self.enter())
for i in range(10):
self.root.bind(str(i), lambda e, n=str(i): self.digit(n))
self.root.bind('.', lambda e: self.decimal())
self.root.bind('+', lambda e: self.add())
self.root.bind('-', lambda e: self.subtract())
self.root.bind('*', lambda e: self.multiply())
self.root.bind('/', lambda e: self.divide())
self.root.bind('<Escape>', lambda e: self.clear())
def digit(self, d):
"""Handle digit button press"""
if self.is_new_entry:
self.entry_buffer = d
self.is_new_entry = False
else:
self.entry_buffer += d
self.update_entry_display()
def decimal(self):
"""Handle decimal point"""
if self.is_new_entry:
self.entry_buffer = "0."
self.is_new_entry = False
elif '.' not in self.entry_buffer:
self.entry_buffer += '.'
self.update_entry_display()
def negate(self):
"""Toggle sign of current entry"""
if self.is_new_entry:
# Negate top of stack
self.execute_code("0 swap -")
else:
if self.entry_buffer.startswith('-'):
self.entry_buffer = self.entry_buffer[1:]
else:
self.entry_buffer = '-' + self.entry_buffer
self.update_entry_display()
def enter(self):
"""Push current entry onto stack"""
if self.entry_buffer:
self.execute_code(self.entry_buffer)
self.entry_buffer = ""
self.is_new_entry = True
self.update_displays()
def add(self):
"""Addition operation"""
self.enter()
self.execute_code("+")
self.update_displays()
def subtract(self):
"""Subtraction operation"""
self.enter()
self.execute_code("-")
self.update_displays()
def multiply(self):
"""Multiplication operation"""
self.enter()
self.execute_code("*")
self.update_displays()
def divide(self):
"""Division operation"""
self.enter()
self.execute_code("/")
self.update_displays()
def sqrt(self):
"""Square root operation"""
self.enter()
self.execute_code("sqrt")
self.update_displays()
def square(self):
"""Square operation"""
self.enter()
self.execute_code("dup *")
self.update_displays()
def reciprocal(self):
"""Reciprocal (1/x) operation"""
self.enter()
self.execute_code("1 swap /")
self.update_displays()
def swap(self):
"""Swap top two stack items"""
self.enter()
self.execute_code("swap")
self.update_displays()
def drop(self):
"""Drop top stack item"""
self.enter()
self.execute_code("drop")
self.update_displays()
def dup(self):
"""Duplicate top stack item"""
self.enter()
self.execute_code("dup")
self.update_displays()
def clear(self):
"""Clear entry and stack"""
self.entry_buffer = ""
self.is_new_entry = True
# Clear the stack
while len(self.interp.stack) > 0:
self.interp.pop()
self.update_displays()
def execute_code(self, code):
"""Execute stack language code"""
try:
self.lexer.source_code = code
self.lexer.pos = 0
self.lexer.column = 1
self.lexer.line = 1
tokens = sls_py.lexical_analysis(self.lexer)
for token in tokens:
if token.type == sls_py.TokenType.EOF:
break
if not self.interp.execute(token):
print(f"Error executing: {code}")
break
except Exception as e:
print(f"Exception: {e}")
def update_entry_display(self):
"""Update the entry display"""
display_text = self.entry_buffer if self.entry_buffer else "0"
self.entry_label.config(text=display_text)
def update_displays(self):
"""Update both entry and stack displays"""
self.update_entry_display()
self.update_stack_display()
def update_stack_display(self):
"""Update the 4-level stack display"""
stack_size = len(self.interp.stack)
for i in range(4):
level = 3 - i # T3, T2, T1, T0
if stack_size > level:
entry = self.interp.stack[-(level + 1)]
value_str = self.format_stack_entry(entry)
self.stack_labels[i].config(text=f"T{level}: {value_str}")
else:
self.stack_labels[i].config(text=f"T{level}: 0")
def format_stack_entry(self, entry):
"""Format a stack entry for display"""
if entry.type == sls_py.StackType.I64:
return str(entry.value)
elif entry.type == sls_py.StackType.DOUBLE:
val = entry.value
# Format with appropriate precision
if abs(val) < 1e-10 and val != 0:
return f"{val:.6e}"
elif abs(val) > 1e10:
return f"{val:.6e}"
else:
return f"{val:.10g}"
elif entry.type == sls_py.StackType.BOOLEAN:
return str(entry.value)
else:
return str(entry.value)
def main():
root = tk.Tk()
app = HPCalculator(root)
root.mainloop()
if __name__ == "__main__":
main()