t-rex 2.3.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/bin/t-rex +696 -32
  3. metadata +20 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9aff5662f6344a4db356673645cf0933b4062a23c92268ae909dc2386d7de479
4
- data.tar.gz: 51586acf9c06c43dd085838582a4446c36b5a9285f59a573fc384c5ed737d305
3
+ metadata.gz: bda263ef3b6b6b69f0272489a81fafc7623b2036a6b59227dd9eb853db87fd67
4
+ data.tar.gz: c6799af1fa2a8948fc55ce5eda92527e5191b63e2d249e141e43d73186eb7156
5
5
  SHA512:
6
- metadata.gz: 0a508633fe0ea2f418ba19b79610e09ebc796ecf15962a0d699349ffb31f560595219cfd1a12aad1fa8416b37c82908b858f0868bc603db3402ff1198292747a
7
- data.tar.gz: 1c1bef9a09d788fdbd81d23e3be1c7cc620ab614026829071376dffdf746a3be2b4e126c54335a9c1334a452b5b919ca7b21a0c3141a1f0d7ff3a52de4403a43
6
+ metadata.gz: 83161d11c2b81512b2107570354b1b90ba8c400737b9984c849cd5b8c8603a261ddf617e90d4bda5a64bfb2a0d5b8a4975b3d37835ba1ed7a23f734a71aa6693
7
+ data.tar.gz: cfcfc97c5c5dcea7ecbc2b11ac836cbafc1d61f0474b6d8fc71bfedb7d17e6a8690cf478785f28c5f265ea2cb7ccbbe336bc487f9c59f7911b0a9150d76b848a
data/bin/t-rex CHANGED
@@ -12,11 +12,52 @@
12
12
  # for any damages resulting from its use. Further, I am under no
13
13
  # obligation to maintain or extend this software. It is provided
14
14
  # on an 'as is' basis without any expressed or implied warranty.
15
- @version = "2.3.2" # Upgraded to new version of rcurses
15
+ @version = "3.0.0" # Major XRPN integration: hybrid mode, enhanced functions, program execution, scrolling
16
16
 
17
17
  require 'rcurses'
18
18
  include Rcurses::Input
19
19
 
20
+ # Try to load XRPN for enhanced functionality
21
+ begin
22
+ # XRPN is not a standard require, it's an executable
23
+ # We need to load the XRPN class directly from the gem
24
+ gem_path = `VISUAL=echo gem open xrpn 2>/dev/null`.chomp
25
+ if gem_path && !gem_path.include?("Unable") && File.exist?(gem_path + "/xlib/xrpn_class")
26
+ # Load the XRPN class
27
+ load gem_path + "/xlib/xrpn_class"
28
+
29
+ # Load essential utility functions (skip string to avoid conflicts with rcurses)
30
+ %w[numeric fact convert_base].each do |lib|
31
+ load gem_path + "/xlib/#{lib}" if File.exist?(gem_path + "/xlib/#{lib}")
32
+ end
33
+
34
+ # Load essential commands that we'll use for integration
35
+ # Load in dependency order: utility functions first, then operations
36
+ essential_commands = %w[
37
+ lift drop dropy enter
38
+ add subtract multiply divide
39
+ ln log exp alog sin cos tan
40
+ sqrt sqr pow fact
41
+ hexdec decbin decoct octdec
42
+ lbl gto gsb rtn xeq stop view pse
43
+ ]
44
+ essential_commands.each do |cmd|
45
+ begin
46
+ load gem_path + "/xcmd/#{cmd}" if File.exist?(gem_path + "/xcmd/#{cmd}")
47
+ rescue => e
48
+ # Skip commands that fail to load
49
+ end
50
+ end
51
+
52
+ XRPN_AVAILABLE = true
53
+ XRPN_GEM_PATH = gem_path
54
+ else
55
+ XRPN_AVAILABLE = false
56
+ end
57
+ rescue => e
58
+ XRPN_AVAILABLE = false
59
+ end
60
+
20
61
  def help # HELP text
21
62
  help = <<HELPTEXT
22
63
 
@@ -38,7 +79,7 @@ help = <<HELPTEXT
38
79
 
39
80
  For Rectangular to Polar conversions:
40
81
  R-P: X value in x, Y in y - yields "θ" in y and "r" in x.
41
- P-R: "θ" in y and "r" in x - yeilds X in x and Y in y.
82
+ P-R: "θ" in y and "r" in x - yields X in x and Y in y.
42
83
 
43
84
  Use the "f" key to set the fixed number of decimal places. Use the "s" key to set
44
85
  the limit for viewing numbers in the "scientific" notation (e.g. 5e+06 for 5000000).
@@ -60,10 +101,32 @@ help = <<HELPTEXT
60
101
 
61
102
  Alternative keys: Left/Right keys (in addition to "<") exchanges X and Y registers.
62
103
  Backspace clears the x register.
104
+
105
+ Help pane scrolling: Shift+UP/DOWN (line by line), PgUP/PgDOWN (page by page),
106
+ HOME/END (top/bottom). Scroll indicators (∆/∇) show available content.
107
+
108
+ == XRPN INTEGRATION ==
109
+ Press 'X' to toggle between T-REX and XRPN calculation modes.
110
+
111
+ To use XRPN features, install the gem first:
112
+ From local repository: cd ~/Main/G/GIT-isene/xrpn && gem build xrpn.gemspec && gem install xrpn-*.gem
113
+ Or when published: gem install xrpn
114
+
115
+ In XRPN mode (press 'X' to toggle), enhanced functions are available:
116
+ - Ctrl+F: Factorial function (with safety limits)
117
+ - Ctrl+B: Number base conversions (hex, bin, oct, dec)
118
+ - P: Program execution panel (load and run XRPN programs) - ESC to exit
119
+ - E: Program editor (create and edit XRPN programs) - ESC to exit
120
+ - Enhanced precision and error handling for all mathematical operations
121
+
122
+ Note: XRPN functions only work when toggled to XRPN mode.
123
+ Both P and E modes support scrolling (Shift+UP/DOWN, PgUP/PgDOWN, HOME/END).
124
+
125
+ XRPN mode provides access to 250+ mathematical functions from the XRPN language.
63
126
  HELPTEXT
64
127
  if @hlp
65
128
  @p_hlp.fg = 239
66
- @p_hlp.ix = 0
129
+ # Don't reset scroll position - preserve current @p_hlp.ix
67
130
  @p_hlp.say(help)
68
131
  else
69
132
  @p_hlp.ix = @history.length - @h + 3
@@ -113,7 +176,7 @@ class Stack # STACK class
113
176
  end
114
177
  def divide
115
178
  begin
116
- throw if self.x == 0
179
+ raise if self.x == 0
117
180
  self.l = self.x
118
181
  self.x = self.y / self.x
119
182
  self.drop
@@ -123,7 +186,7 @@ class Stack # STACK class
123
186
  end
124
187
  def mod
125
188
  begin
126
- throw if self.x == 0
189
+ raise if self.x == 0
127
190
  self.l = self.x
128
191
  self.x = self.y % self.x
129
192
  self.drop
@@ -133,7 +196,7 @@ class Stack # STACK class
133
196
  end
134
197
  def percent
135
198
  begin
136
- throw if self.y == 0
199
+ raise if self.y == 0
137
200
  self.l = self.x
138
201
  self.x = 100*(self.x / self.y)
139
202
  self.drop
@@ -165,7 +228,7 @@ class Stack # STACK class
165
228
  end
166
229
  def recip
167
230
  begin
168
- throw if self.x == 0
231
+ raise if self.x == 0
169
232
  self.l = self.x
170
233
  self.x = 1 / self.x
171
234
  rescue
@@ -252,7 +315,7 @@ class Stack # STACK class
252
315
  end
253
316
  def rp
254
317
  begin
255
- throw if self.x == 0
318
+ raise if self.x == 0
256
319
  self.l = self.x
257
320
  x = self.x
258
321
  y = self.y
@@ -278,6 +341,145 @@ class Stack # STACK class
278
341
  end
279
342
  end
280
343
 
344
+ class XrpnInterface # XRPN INTEGRATION CLASS
345
+ attr_accessor :calc, :mode
346
+
347
+ def initialize
348
+ if XRPN_AVAILABLE
349
+ @calc = XRPN.new(nil) # XRPN class expects a file parameter, use nil for no file
350
+ @mode = :trex # :trex or :xrpn
351
+ @available = true
352
+ else
353
+ @calc = nil
354
+ @mode = :trex
355
+ @available = false
356
+ end
357
+ end
358
+
359
+ def available?
360
+ @available
361
+ end
362
+
363
+ def toggle_mode
364
+ return "XRPN not available" unless @available
365
+ @mode = (@mode == :trex) ? :xrpn : :trex
366
+ end
367
+
368
+ def sync_stack_to_xrpn(stack)
369
+ return unless @available
370
+ @calc.t = stack.t
371
+ @calc.z = stack.z
372
+ @calc.y = stack.y
373
+ @calc.x = stack.x
374
+ end
375
+
376
+ def sync_stack_from_xrpn
377
+ return [0, 0, 0, 0] unless @available
378
+ return [@calc.t, @calc.z, @calc.y, @calc.x]
379
+ end
380
+
381
+ def execute_xrpn_command(cmd)
382
+ return "XRPN not available" unless @available
383
+ begin
384
+ # Check if it's a number first
385
+ if cmd =~ /^-?\d+(\.\d+)?$/ || cmd =~ /^-?\.\d+$/
386
+ # It's a number, enter it into the stack
387
+ @calc.send('lift') if @calc.respond_to?('lift')
388
+ @calc.x = cmd.to_f
389
+ return true
390
+ elsif @calc.respond_to?(cmd.downcase)
391
+ # It's a command, execute it
392
+ @calc.send(cmd.downcase)
393
+ return true
394
+ else
395
+ return "Command not found: #{cmd}"
396
+ end
397
+ rescue => e
398
+ return "Error: #{e.message}"
399
+ end
400
+ end
401
+
402
+ def get_xrpn_functions
403
+ {
404
+ # Enhanced scientific functions
405
+ 'sinh' => 'SINH',
406
+ 'cosh' => 'COSH',
407
+ 'tanh' => 'TANH',
408
+ 'asinh' => 'ASINH',
409
+ 'acosh' => 'ACOSH',
410
+ 'atanh' => 'ATANH',
411
+ 'gamma' => 'GAMMA',
412
+ 'factorial' => '!',
413
+ 'combination' => 'COMB',
414
+ 'permutation' => 'PERM',
415
+ 'gcd' => 'GCD',
416
+ 'lcm' => 'LCM',
417
+ # Number base conversions
418
+ 'hex' => 'HEX',
419
+ 'bin' => 'BIN',
420
+ 'oct' => 'OCT',
421
+ 'dec' => 'DEC',
422
+ # Statistical functions
423
+ 'mean' => 'MEAN',
424
+ 'sdev' => 'SDEV',
425
+ 'sum' => 'SUM',
426
+ # Complex numbers
427
+ 'real' => 'REAL',
428
+ 'imag' => 'IMAG',
429
+ 'conj' => 'CONJ',
430
+ 'arg' => 'ARG'
431
+ }
432
+ end
433
+
434
+ def mode_display
435
+ if @available
436
+ @mode == :xrpn ? "XRPN" : "T-REX"
437
+ else
438
+ "T-REX (XRPN N/A)"
439
+ end
440
+ end
441
+
442
+ def debug_status
443
+ "Available: #{@available}, Mode: #{@mode}"
444
+ end
445
+
446
+ def execute_with_sync(xrpn_cmd, stack)
447
+ return "XRPN not available" unless @available
448
+ sync_stack_to_xrpn(stack)
449
+ result = execute_xrpn_command(xrpn_cmd)
450
+ if result == true
451
+ return sync_stack_from_xrpn
452
+ else
453
+ return result
454
+ end
455
+ end
456
+
457
+ def load_program(filename)
458
+ return "XRPN not available" unless @available
459
+ begin
460
+ @calc.load_program(filename)
461
+ return true
462
+ rescue => e
463
+ return "Error: #{e.message}"
464
+ end
465
+ end
466
+
467
+ def execute_program(program_name)
468
+ return "XRPN not available" unless @available
469
+ begin
470
+ @calc.execute_program(program_name)
471
+ return true
472
+ rescue => e
473
+ return "Error: #{e.message}"
474
+ end
475
+ end
476
+
477
+ def list_programs
478
+ return [] unless @available
479
+ @calc.list_programs
480
+ end
481
+ end
482
+
281
483
  begin # BASIC setup
282
484
  @stk = Stack.new(0, 0, 0, 0, 0)
283
485
  @reg = %w[0 0 0 0 0 0 0 0 0 0]
@@ -286,6 +488,7 @@ begin # BASIC setup
286
488
  @dot = true
287
489
  @mod = "Deg"
288
490
  @hlp = true
491
+ @xrpn = XrpnInterface.new
289
492
 
290
493
  load(Dir.home+'/.t-rex.conf') if File.exist?(Dir.home+'/.t-rex.conf')
291
494
  @mod == "Deg" ? @stk.deg = true : @stk.deg = false
@@ -296,20 +499,26 @@ begin # BASIC setup
296
499
  end
297
500
 
298
501
  begin # PANE setup
299
- @h, @w = IO.console.winsize
502
+ begin
503
+ @h, @w = IO.console.winsize
504
+ rescue
505
+ @h, @w = 24, 80 # Default fallback
506
+ end
300
507
 
301
508
  # pane = Rcurses::Pane.new( x, y, width, height, fg, bg)
302
509
  @p_bck = Rcurses::Pane.new( 1, 1, @w, @h, 235, 235) # Back
303
510
  @p_inf = Rcurses::Pane.new( 2, 2, 32, 1, 168, 238) # Info-line
304
511
  @p_lbl = Rcurses::Pane.new( 2, 3, 1, 5, 250, 238) # LTZYX lables
305
- @p_key = Rcurses::Pane.new( 2, 9, 32, 11, 0, 0) # Key panel
306
- @p_reg = Rcurses::Pane.new( 2, 21, 32, @h - 21, 242, 0) # Regs
512
+ @p_key = Rcurses::Pane.new( 2, 9, 32, 14, 0, 0) # Key panel
513
+ @p_reg = Rcurses::Pane.new( 2, 24, 32, @h - 24, 242, 0) # Regs
307
514
  @p_hlp = Rcurses::Pane.new( 35, 2, @w - 35, @h - 2, 239, 0) # Help
515
+ @p_hlp.scroll = true # Enable scroll indicators
308
516
  @p_l = Rcurses::Pane.new( 4, 3, 30, 1, 60, 235) # L
309
517
  @p_t = Rcurses::Pane.new( 4, 4, 30, 1, 68, 233) # T
310
518
  @p_z = Rcurses::Pane.new( 4, 5, 30, 1, 75, 233) # Z
311
519
  @p_y = Rcurses::Pane.new( 4, 6, 30, 1, 117, 233) # Y
312
520
  @p_x = Rcurses::Pane.new( 4, 7, 30, 1, 7, 0) # X
521
+ @p_x.scroll = false # Disable scroll indicators for X register
313
522
 
314
523
  @p_lbl.align = "r"
315
524
  @p_l.align = "r"
@@ -341,7 +550,11 @@ begin # PANE setup
341
550
  keys += " r".k + "ad".t + " d".k + "eg".t + " c".k + "lx".t
342
551
  keys += "\n\n"
343
552
  keys += " S".k + "to".t + " R".k + "cl".t + " s".k + "ci".t + " f".k + "ix".t
344
- keys += " u".k + "ndo".t + " H".k + "lp".t + " Q".k + "uit".t
553
+ keys += " u".k + "ndo".t + " H".k + "lp".t + " Q".k + "uit".t
554
+ keys += "\n\n"
555
+ keys += " X".k + "rpn".t + " (when in xrpn mode):".s
556
+ keys += "\n"
557
+ keys += " ^F".k + "act".t + " ^B".k + "ase".t + " P".k + "rog".t + " E".k + "dit".t
345
558
  @p_key.text = keys
346
559
  end
347
560
 
@@ -393,6 +606,24 @@ def main_getkey(c) # GET KEY FROM USER
393
606
  when 'DOWN' # Roll stack down
394
607
  @stk.rdn
395
608
  history("↓")
609
+ when 'S-UP' # Scroll help pane up
610
+ @p_hlp.lineup
611
+ @scroll_action = true
612
+ when 'S-DOWN' # Scroll help pane down
613
+ @p_hlp.linedown
614
+ @scroll_action = true
615
+ when 'PgUP' # Page up in help pane
616
+ @p_hlp.pageup
617
+ @scroll_action = true
618
+ when 'PgDOWN' # Page down in help pane
619
+ @p_hlp.pagedown
620
+ @scroll_action = true
621
+ when 'HOME' # Go to top of help pane
622
+ @p_hlp.top if @hlp
623
+ @scroll_action = true
624
+ when 'END' # Go to bottom of help pane
625
+ @p_hlp.bottom if @hlp
626
+ @scroll_action = true
396
627
  when '<', 'LEFT', 'RIGHT' # x<>y
397
628
  @stk.xy
398
629
  history("x<>y")
@@ -445,18 +676,54 @@ def main_getkey(c) # GET KEY FROM USER
445
676
  @stk.cube
446
677
  history("x^3")
447
678
  when 'n' # ln(x)
448
- e = @stk.ln
679
+ if @xrpn.mode == :xrpn
680
+ result = @xrpn.execute_with_sync("ln", @stk)
681
+ if result.is_a?(Array)
682
+ @stk.t, @stk.z, @stk.y, @stk.x = result
683
+ else
684
+ error(result)
685
+ end
686
+ else
687
+ e = @stk.ln
688
+ error ("Error: Negative x") if e == "Error"
689
+ end
449
690
  history("ln")
450
- error ("Error: Negative x") if e == "Error"
451
691
  when 'C-N' # e^x
452
- @stk.ex
692
+ if @xrpn.mode == :xrpn
693
+ result = @xrpn.execute_with_sync("exp", @stk)
694
+ if result.is_a?(Array)
695
+ @stk.t, @stk.z, @stk.y, @stk.x = result
696
+ else
697
+ error(result)
698
+ end
699
+ else
700
+ @stk.ex
701
+ end
453
702
  history("e^x")
454
703
  when 'g' # log(x)
455
- e = @stk.log
704
+ if @xrpn.mode == :xrpn
705
+ result = @xrpn.execute_with_sync("log", @stk)
706
+ if result.is_a?(Array)
707
+ @stk.t, @stk.z, @stk.y, @stk.x = result
708
+ else
709
+ error(result)
710
+ end
711
+ else
712
+ e = @stk.log
713
+ error ("Error: Negative x") if e == "Error"
714
+ end
456
715
  history("log")
457
- error ("Error: Negative x") if e == "Error"
458
716
  when 'C-G' # 10^x
459
- @stk.tenx
717
+ if @xrpn.mode == :xrpn
718
+ result = @xrpn.execute_with_sync("alog", @stk)
719
+ if result.is_a?(Array)
720
+ @stk.t, @stk.z, @stk.y, @stk.x = result
721
+ else
722
+ error(result)
723
+ end
724
+ else
725
+ @stk.tenx
726
+ end
460
727
  history("10^x")
461
728
  when 'l' # Recall Lastx (l) to x
462
729
  @stk.lift
@@ -580,12 +847,92 @@ def main_getkey(c) # GET KEY FROM USER
580
847
  when 'H' # Toggle help/histprint
581
848
  @hlp = !@hlp
582
849
  when 'C-B'
583
- if !@hlp
850
+ # Check if we're in XRPN mode first
851
+ if @xrpn.available? && @xrpn.mode == :xrpn
852
+ # XRPN base conversion mode
853
+ @p_hlp.fg = 168
854
+ @p_hlp.clear
855
+ @p_hlp.text = "XRPN Base Conversion\n\nChoose conversion:\n"
856
+ @p_hlp.text += " h - Convert to hexadecimal\n"
857
+ @p_hlp.text += " b - Convert to binary\n"
858
+ @p_hlp.text += " o - Convert to octal\n"
859
+ @p_hlp.text += " d - Convert to decimal\n"
860
+ @p_hlp.text += " ESC - Cancel\n\n"
861
+ @p_hlp.text += "Choice> "
862
+ @p_hlp.refresh
863
+
864
+ f = getchr
865
+
866
+ output = "\n"
867
+ conversion_done = false
868
+ case f
869
+ when 'h'
870
+ result = @xrpn.execute_with_sync("dechex", @stk)
871
+ if result.is_a?(Array)
872
+ @stk.t, @stk.z, @stk.y, @stk.x = result
873
+ output += "Converted to hexadecimal: #{@stk.x}\n"
874
+ conversion_done = true
875
+ pstack # Update stack display
876
+ else
877
+ output += "Error: #{result}\n"
878
+ end
879
+ history("hex")
880
+ when 'b'
881
+ result = @xrpn.execute_with_sync("decbin", @stk)
882
+ if result.is_a?(Array)
883
+ @stk.t, @stk.z, @stk.y, @stk.x = result
884
+ output += "Converted to binary: #{@stk.x}\n"
885
+ conversion_done = true
886
+ pstack # Update stack display
887
+ else
888
+ output += "Error: #{result}\n"
889
+ end
890
+ history("bin")
891
+ when 'o'
892
+ result = @xrpn.execute_with_sync("decoct", @stk)
893
+ if result.is_a?(Array)
894
+ @stk.t, @stk.z, @stk.y, @stk.x = result
895
+ output += "Converted to octal: #{@stk.x}\n"
896
+ conversion_done = true
897
+ pstack # Update stack display
898
+ else
899
+ output += "Error: #{result}\n"
900
+ end
901
+ history("oct")
902
+ when 'd'
903
+ result = @xrpn.execute_with_sync("hexdec", @stk)
904
+ if result.is_a?(Array)
905
+ @stk.t, @stk.z, @stk.y, @stk.x = result
906
+ output += "Converted to decimal: #{@stk.x}\n"
907
+ conversion_done = true
908
+ pstack # Update stack display
909
+ else
910
+ output += "Error: #{result}\n"
911
+ end
912
+ history("dec")
913
+ when 'ESC'
914
+ output += "Conversion cancelled\n"
915
+ else
916
+ output += "Invalid choice\n"
917
+ end
918
+
919
+ # Show result
920
+ @p_hlp.text += output
921
+ @p_hlp.text += "\nPress any key to continue..."
922
+ @p_hlp.refresh
923
+ getchr
924
+ @hlp ? help : @p_hlp.clear
925
+ @p_hlp.refresh
926
+ elsif !@hlp
927
+ # Original T-REX history browsing
584
928
  loop do
585
929
  @p_hlp.ix -= @h + 3
586
930
  histprint
587
931
  break if getchr != 'C-B'
588
932
  end
933
+ else
934
+ # Help mode, provide user feedback
935
+ error("Ctrl+B: In XRPN mode for base conversion, or history browsing when not in help")
589
936
  end
590
937
  when 'C-P'
591
938
  cont = histprint.pure
@@ -612,6 +959,318 @@ def main_getkey(c) # GET KEY FROM USER
612
959
  @p_hlp.fg = 239
613
960
  getchr
614
961
  @hlp ? help : @p_hlp.say("")
962
+ when 'X' # Toggle T-REX/XRPN mode
963
+ if @xrpn.available?
964
+ if @xrpn.mode == :trex
965
+ @xrpn.sync_stack_to_xrpn(@stk)
966
+ result = @xrpn.toggle_mode
967
+ if result.is_a?(String)
968
+ error(result)
969
+ else
970
+ history("Mode: XRPN")
971
+ end
972
+ else
973
+ t, z, y, x = @xrpn.sync_stack_from_xrpn
974
+ @stk.t, @stk.z, @stk.y, @stk.x = t, z, y, x
975
+ @xrpn.toggle_mode
976
+ history("Mode: T-REX")
977
+ end
978
+ else
979
+ error("XRPN not available. See help for installation instructions.")
980
+ end
981
+ when 'C-F' # Factorial (XRPN only)
982
+ if @xrpn.available? && @xrpn.mode == :xrpn
983
+ # Safety check for huge factorials that can freeze the app
984
+ if @stk.x > 170
985
+ error("Error: Factorial too large (max 170)")
986
+ elsif @stk.x < 0
987
+ error("Error: Factorial of negative number")
988
+ elsif @stk.x != @stk.x.to_i
989
+ error("Error: Factorial requires integer")
990
+ else
991
+ result = @xrpn.execute_with_sync("fact", @stk)
992
+ if result.is_a?(Array)
993
+ @stk.t, @stk.z, @stk.y, @stk.x = result
994
+ else
995
+ error(result)
996
+ end
997
+ history("fact")
998
+ end
999
+ end
1000
+ when 'P' # XRPN Program execution (XRPN only)
1001
+ if @xrpn.available? && @xrpn.mode == :xrpn
1002
+ @p_hlp.fg = 168
1003
+ @p_hlp.clear
1004
+ program_mode = true
1005
+
1006
+ # Display instructions
1007
+ @p_hlp.text = "XRPN Program Execution\n\nCommands:\n"
1008
+ @p_hlp.text += " l <file> - Load XRPN program from file\n"
1009
+ @p_hlp.text += " x <name> - Execute program by name\n"
1010
+ @p_hlp.text += " r <cmd> - Run single XRPN command\n"
1011
+ @p_hlp.text += " s - Show current stack\n"
1012
+ @p_hlp.text += " p - List loaded programs\n"
1013
+ @p_hlp.text += " status - Show XRPN status\n"
1014
+ @p_hlp.text += " ESC - Exit program mode\n\n"
1015
+ @p_hlp.text += "Program> "
1016
+ @p_hlp.refresh
1017
+
1018
+ input_buffer = ""
1019
+ while program_mode
1020
+ chr = getchr
1021
+ case chr
1022
+ when 'ESC'
1023
+ program_mode = false
1024
+ when 'S-UP' # Scroll up in program mode
1025
+ @p_hlp.lineup
1026
+ when 'S-DOWN' # Scroll down in program mode
1027
+ @p_hlp.linedown
1028
+ when 'PgUP' # Page up in program mode
1029
+ @p_hlp.pageup
1030
+ when 'PgDOWN' # Page down in program mode
1031
+ @p_hlp.pagedown
1032
+ when 'HOME' # Go to top in program mode
1033
+ @p_hlp.top
1034
+ when 'END' # Go to bottom in program mode
1035
+ @p_hlp.bottom
1036
+ when 'ENTER'
1037
+ cmd = input_buffer.strip
1038
+ input_buffer = ""
1039
+
1040
+ # Process command
1041
+ output = "\n"
1042
+ case cmd.split.first
1043
+ when 'l' # Load program
1044
+ filename = cmd.split[1]
1045
+ if filename
1046
+ result = @xrpn.load_program(filename)
1047
+ output += (result == true) ? "Program loaded successfully\n" : "#{result}\n"
1048
+ else
1049
+ output += "Usage: l <filename>\n"
1050
+ end
1051
+ when 'x' # Execute program
1052
+ program_name = cmd.split[1]
1053
+ if program_name
1054
+ @xrpn.sync_stack_to_xrpn(@stk)
1055
+ result = @xrpn.execute_program(program_name)
1056
+ if result == true
1057
+ t, z, y, x = @xrpn.sync_stack_from_xrpn
1058
+ @stk.t, @stk.z, @stk.y, @stk.x = t, z, y, x
1059
+ output += "Program executed successfully\n"
1060
+ pstack # Update stack display
1061
+ else
1062
+ output += "#{result}\n"
1063
+ end
1064
+ else
1065
+ output += "Usage: x <program_name>\n"
1066
+ end
1067
+ when 'r' # Run single command
1068
+ xrpn_cmd = cmd[2..-1]
1069
+ if xrpn_cmd && !xrpn_cmd.strip.empty?
1070
+ @xrpn.sync_stack_to_xrpn(@stk)
1071
+ result = @xrpn.execute_xrpn_command(xrpn_cmd.strip)
1072
+ if result == true
1073
+ t, z, y, x = @xrpn.sync_stack_from_xrpn
1074
+ @stk.t, @stk.z, @stk.y, @stk.x = t, z, y, x
1075
+ output += "Command '#{xrpn_cmd.strip}' executed successfully\n"
1076
+ pstack # Update stack display
1077
+ else
1078
+ output += "#{result}\n"
1079
+ end
1080
+ else
1081
+ output += "Usage: r <xrpn_command>\n"
1082
+ end
1083
+ when 's' # Show stack
1084
+ output += "Stack: T=#{@stk.t} Z=#{@stk.z} Y=#{@stk.y} X=#{@stk.x}\n"
1085
+ when 'p' # List programs
1086
+ programs = @xrpn.list_programs
1087
+ output += programs.empty? ? "No programs loaded\n" : "Loaded programs: #{programs.join(', ')}\n"
1088
+ when 'status' # Show XRPN status
1089
+ output += "XRPN Status: #{@xrpn.debug_status}\n"
1090
+ output += "Stack sync: T=#{@stk.t} Z=#{@stk.z} Y=#{@stk.y} X=#{@stk.x}\n"
1091
+ when nil, ''
1092
+ # Empty command, do nothing
1093
+ else
1094
+ output += "Unknown command. Use ESC to exit.\n"
1095
+ end
1096
+
1097
+ # Update display
1098
+ @p_hlp.text += output + "\nProgram> "
1099
+ @p_hlp.refresh
1100
+ when 'BACK', 'DEL'
1101
+ unless input_buffer.empty?
1102
+ input_buffer.chop!
1103
+ @p_hlp.text = @p_hlp.text[0..-2] # Remove last character
1104
+ @p_hlp.refresh
1105
+ end
1106
+ when String
1107
+ if chr.length == 1 && chr =~ /[ -~]/ # Printable characters
1108
+ input_buffer += chr
1109
+ @p_hlp.text += chr
1110
+ @p_hlp.refresh
1111
+ end
1112
+ end
1113
+ end
1114
+
1115
+ @p_hlp.fg = 239
1116
+ @hlp ? help : @p_hlp.clear
1117
+ @p_hlp.refresh
1118
+ end
1119
+ when 'E' # XRPN Program editor (XRPN only)
1120
+ if @xrpn.available? && @xrpn.mode == :xrpn
1121
+ @p_hlp.fg = 168
1122
+ @p_hlp.clear
1123
+ editor_mode = true
1124
+ current_program = []
1125
+ program_name = ""
1126
+
1127
+ # Display instructions
1128
+ @p_hlp.text = "XRPN Program Editor\n\nCommands:\n"
1129
+ @p_hlp.text += " new <name> - Create new program\n"
1130
+ @p_hlp.text += " add <line> - Add line to current program\n"
1131
+ @p_hlp.text += " list - List current program\n"
1132
+ @p_hlp.text += " del <n> - Delete line n\n"
1133
+ @p_hlp.text += " save - Save current program\n"
1134
+ @p_hlp.text += " run - Run current program\n"
1135
+ @p_hlp.text += " clear - Clear current program\n"
1136
+ @p_hlp.text += " ESC - Exit editor\n\n"
1137
+ @p_hlp.text += "Editor> "
1138
+ @p_hlp.refresh
1139
+
1140
+ input_buffer = ""
1141
+ while editor_mode
1142
+ chr = getchr
1143
+ case chr
1144
+ when 'ESC'
1145
+ editor_mode = false
1146
+ when 'S-UP' # Scroll up in editor mode
1147
+ @p_hlp.lineup
1148
+ when 'S-DOWN' # Scroll down in editor mode
1149
+ @p_hlp.linedown
1150
+ when 'PgUP' # Page up in editor mode
1151
+ @p_hlp.pageup
1152
+ when 'PgDOWN' # Page down in editor mode
1153
+ @p_hlp.pagedown
1154
+ when 'HOME' # Go to top in editor mode
1155
+ @p_hlp.top
1156
+ when 'END' # Go to bottom in editor mode
1157
+ @p_hlp.bottom
1158
+ when 'ENTER'
1159
+ cmd = input_buffer.strip
1160
+ input_buffer = ""
1161
+
1162
+ # Process command
1163
+ output = "\n"
1164
+ case cmd.split.first
1165
+ when 'new' # New program
1166
+ program_name = cmd.split[1]
1167
+ if program_name
1168
+ current_program = []
1169
+ output += "New program '#{program_name}' created\n"
1170
+ else
1171
+ output += "Usage: new <program_name>\n"
1172
+ end
1173
+ when 'add' # Add line
1174
+ line = cmd[4..-1]
1175
+ if line && !line.strip.empty? && !program_name.empty?
1176
+ current_program << line.strip
1177
+ output += "Line #{current_program.length} added: #{line.strip}\n"
1178
+ elsif program_name.empty?
1179
+ output += "Create a program first with 'new <name>'\n"
1180
+ else
1181
+ output += "Usage: add <program_line>\n"
1182
+ end
1183
+ when 'list' # List program
1184
+ if current_program.empty?
1185
+ output += "No program loaded\n"
1186
+ else
1187
+ output += "Program '#{program_name}':\n"
1188
+ current_program.each_with_index do |line, i|
1189
+ output += " #{i+1}: #{line}\n"
1190
+ end
1191
+ end
1192
+ when 'del' # Delete line
1193
+ line_num = cmd.split[1].to_i
1194
+ if line_num > 0 && line_num <= current_program.length
1195
+ deleted = current_program.delete_at(line_num - 1)
1196
+ output += "Deleted line #{line_num}: #{deleted}\n"
1197
+ else
1198
+ output += "Invalid line number\n"
1199
+ end
1200
+ when 'save' # Save program
1201
+ if !current_program.empty? && !program_name.empty?
1202
+ program_text = current_program.join("\n")
1203
+ filename = "#{program_name}.xrpn"
1204
+ begin
1205
+ File.write(filename, program_text)
1206
+ result = @xrpn.load_program(filename)
1207
+ output += (result == true) ? "Program saved and loaded: #{filename}\n" : "Saved but load failed: #{result}\n"
1208
+ rescue => e
1209
+ output += "Save failed: #{e.message}\n"
1210
+ end
1211
+ else
1212
+ output += "No program to save\n"
1213
+ end
1214
+ when 'run' # Run program
1215
+ if !current_program.empty?
1216
+ @xrpn.sync_stack_to_xrpn(@stk)
1217
+ output += "Executing program line by line:\n"
1218
+ success = true
1219
+ current_program.each_with_index do |line, i|
1220
+ line = line.strip
1221
+ next if line.empty?
1222
+ result = @xrpn.execute_xrpn_command(line)
1223
+ if result == true
1224
+ output += " #{i+1}: #{line} - OK\n"
1225
+ else
1226
+ output += " #{i+1}: #{line} - #{result}\n"
1227
+ success = false
1228
+ break
1229
+ end
1230
+ end
1231
+ if success
1232
+ t, z, y, x = @xrpn.sync_stack_from_xrpn
1233
+ @stk.t, @stk.z, @stk.y, @stk.x = t, z, y, x
1234
+ output += "Program executed successfully\n"
1235
+ pstack # Update stack display
1236
+ else
1237
+ output += "Program execution stopped due to error\n"
1238
+ end
1239
+ else
1240
+ output += "No program to run\n"
1241
+ end
1242
+ when 'clear' # Clear program
1243
+ current_program = []
1244
+ program_name = ""
1245
+ output += "Program cleared\n"
1246
+ when nil, ''
1247
+ # Empty command, do nothing
1248
+ else
1249
+ output += "Unknown command. Use ESC to exit.\n"
1250
+ end
1251
+
1252
+ # Update display
1253
+ @p_hlp.text += output + "\nEditor> "
1254
+ @p_hlp.refresh
1255
+ when 'BACK', 'DEL'
1256
+ unless input_buffer.empty?
1257
+ input_buffer.chop!
1258
+ @p_hlp.text = @p_hlp.text[0..-2] # Remove last character
1259
+ @p_hlp.refresh
1260
+ end
1261
+ when String
1262
+ if chr.length == 1 && chr =~ /[ -~]/ # Printable characters
1263
+ input_buffer += chr
1264
+ @p_hlp.text += chr
1265
+ @p_hlp.refresh
1266
+ end
1267
+ end
1268
+ end
1269
+
1270
+ @p_hlp.fg = 239
1271
+ @hlp ? help : @p_hlp.clear
1272
+ @p_hlp.refresh
1273
+ end
615
1274
  when 'Q' # QUIT
616
1275
  Rcurses.clear_screen
617
1276
  Rcurses::Cursor.show
@@ -622,7 +1281,6 @@ def main_getkey(c) # GET KEY FROM USER
622
1281
  number, c = entry(chr)
623
1282
  if number != ""
624
1283
  @stk.l = @stk.x
625
- @stk.lift unless @stk.x == 0
626
1284
  @stk.x = number
627
1285
  end
628
1286
  history(number.to_s)
@@ -636,11 +1294,12 @@ end
636
1294
  def entry(chr) # X REGISTER ENTRY
637
1295
  num = chr
638
1296
  pos = 1
639
- Rcurses::Cursor.set(7,33)
640
- Rcurses::Cursor.show
641
1297
  while %w[0 1 2 3 4 5 6 7 8 9 . , h H e RIGHT LEFT HOME END DEL BACK WBACK LDEL].include?(chr)
642
1298
  @p_x.clear
643
1299
  @p_x.say(num)
1300
+ # Position cursor at the right place in the X register (row 7, right-aligned in 30-char width)
1301
+ cursor_col = 33 - num.length + pos
1302
+ Rcurses::Cursor.set(7, cursor_col)
644
1303
  Rcurses::Cursor.show
645
1304
  chr = getchr
646
1305
  case chr
@@ -693,9 +1352,7 @@ def entry(chr) # X REGISTER ENTRY
693
1352
  when /[0-9.,]/
694
1353
  num.insert(pos,chr)
695
1354
  pos += 1
696
- @p_x.say(num)
697
1355
  end
698
- Rcurses::Cursor.col(33 - num.length + pos)
699
1356
  end
700
1357
  num = "" if %w[DOWN UP].include?(chr)
701
1358
  num.gsub!(/,/, '.')
@@ -736,8 +1393,10 @@ def pregs # PRINT CONTENT OF REGS (0-9)
736
1393
  @p_reg.refresh
737
1394
  end
738
1395
  def error(err) # PRINT ERRORS TO X
739
- @p_x.say(err)
740
- @history.insert(-2, err)
1396
+ # Truncate error message to fit in X register pane (width 30)
1397
+ truncated_err = err.length > 30 ? err[0..26] + "..." : err
1398
+ @p_x.say(truncated_err)
1399
+ @history.insert(-2, err) # Store full error in history
741
1400
  getchr
742
1401
  end
743
1402
  def conf_write # WRITE TO .t-rex.conf
@@ -758,18 +1417,23 @@ refresh
758
1417
  Rcurses::Cursor.hide
759
1418
  begin # Capture main loop
760
1419
  loop do # Main loop
761
- @p_inf.say(" #{@mod} Sci=#{@sci} Fix=#{@fix}".i)
1420
+ @p_inf.say(" #{@xrpn.mode_display} #{@mod} Sci=#{@sci} Fix=#{@fix}".i)
762
1421
  pstack
763
1422
  pregs
764
1423
  @t = @stk.dup
1424
+ @scroll_action = false
765
1425
  main_getkey("") # Get key input from user
766
- help
1426
+ help unless @scroll_action
767
1427
  @u.push(@t.dup) if @t != @stk and @undo == false
768
1428
  @undo = false
769
- h1, w1 = IO.console.winsize
770
- if h1 != @h or w1 != @w # Refresh on terminal resize
771
- @h, @w = IO.console.winsize
772
- refresh
1429
+ begin
1430
+ h1, w1 = IO.console.winsize
1431
+ if h1 != @h or w1 != @w # Refresh on terminal resize
1432
+ @h, @w = h1, w1
1433
+ refresh
1434
+ end
1435
+ rescue
1436
+ # Ignore winsize errors in non-terminal environments
773
1437
  end
774
1438
  end
775
1439
  ensure # Write to .t-rex.conf on exit
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: t-rex
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.2
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Geir Isene
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-04-14 00:00:00.000000000 Z
11
+ date: 2025-07-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rcurses
@@ -24,10 +24,26 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: xrpn
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
27
41
  description: 'This is a terminal curses RPN calculator similar to the traditional
28
42
  calculators from Hewlett Packard. See https://www.hpmuseum.org/rpn.htm for info
29
- on RPN (Reverse Polish Notation). New in 2.3: Upgraded to new version of rcurses.
30
- 2.3.2: Fix for rcurses.'
43
+ on RPN (Reverse Polish Notation). New in 3.0: Major XRPN integration with hybrid
44
+ calculation modes, enhanced mathematical functions, program execution environment,
45
+ program editor, base conversions, factorial safety, and comprehensive scrolling
46
+ support.'
31
47
  email: g@isene.com
32
48
  executables:
33
49
  - t-rex