#!/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), ('x²', 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('', lambda e: self.enter()) self.root.bind('', 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('', 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()