diff --git a/SLS_Python/sls_calc/__init__.py b/SLS_Python/sls_calc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/SLS_Python/sls_calc/__main__.py b/SLS_Python/sls_calc/__main__.py new file mode 100644 index 0000000..e8565f3 --- /dev/null +++ b/SLS_Python/sls_calc/__main__.py @@ -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), ('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()