#!/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 SlsCalculator: def __init__(self, root): self.root = root self.root.title("SLS 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.create_widgets() 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)) # type: ignore 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)) # type: ignore self.stack_labels = [] for i in range(4): label = ttk.Label(stack_frame, text=f"{3-i}:", anchor='e', width=30) label.grid(row=i, column=0, sticky=(tk.W, tk.E), pady=2) # type: ignore 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)) # type: ignore self.entry_label = ttk.Label(entry_frame, text="0", anchor='e', font=('Courier', 16)) self.entry_label.grid(row=0, column=0, sticky=(tk.W, tk.E)) # type: ignore def create_button_grid(self, parent): """Create the calculator button grid""" buttons = [ [('√', self.sqrt), ('x²', self.square), ('1/x', self.reciprocal), ('NULL', lambda: None)], [('ENTER', lambda: self.enter(True)), ('SWAP', self.swap), ('<-', self.drop), ('NULL', lambda: None)], [('7', lambda: self.digit('7')), ('8', lambda: self.digit('8')), ('9', lambda: self.digit('9')), ('÷', self.divide)], [('4', lambda: self.digit('4')), ('5', lambda: self.digit('5')), ('6', lambda: self.digit('6')), ('×', self.multiply)], [('1', lambda: self.digit('1')), ('2', lambda: self.digit('2')), ('3', lambda: self.digit('3')), ('−', self.subtract)], [('0', lambda: self.digit('0')), ('.', self.decimal), ('±', self.negate), ('+', self.add)], ] for row_idx, row in enumerate(buttons, start=2): for col_idx, (text, command) in enumerate(row): if text != "NULL": btn = ttk.Button(parent, text=text, command=command, width=8) btn.grid(row=row_idx, column=col_idx, padx=2, pady=2, sticky=(tk.W, tk.E, tk.N, tk.S)) # type: ignore # Keyboard bindings self.root.bind('', lambda e: self.drop()) self.root.bind('', lambda e: self.enter(True)) self.root.bind('', lambda e: self.enter(True)) 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()) 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, key=False): """Push current entry onto stack""" if self.entry_buffer: self.execute_code(self.entry_buffer) self.entry_buffer = "" self.is_new_entry = True elif key: self.execute_code("dup") 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 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"{level}: {value_str}") else: self.stack_labels[i].config(text=f"{level}:") 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 = SlsCalculator(root) root.mainloop() if __name__ == "__main__": main()