t-rex 2.3.3 → 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.
- checksums.yaml +4 -4
- data/bin/t-rex +693 -27
- metadata +20 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bda263ef3b6b6b69f0272489a81fafc7623b2036a6b59227dd9eb853db87fd67
|
4
|
+
data.tar.gz: c6799af1fa2a8948fc55ce5eda92527e5191b63e2d249e141e43d73186eb7156
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 = "
|
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 -
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
306
|
-
@p_reg = Rcurses::Pane.new( 2,
|
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
|
-
|
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
|
-
@
|
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
|
-
|
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
|
-
@
|
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
|
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
|
@@ -734,8 +1393,10 @@ def pregs # PRINT CONTENT OF REGS (0-9)
|
|
734
1393
|
@p_reg.refresh
|
735
1394
|
end
|
736
1395
|
def error(err) # PRINT ERRORS TO X
|
737
|
-
|
738
|
-
|
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
|
739
1400
|
getchr
|
740
1401
|
end
|
741
1402
|
def conf_write # WRITE TO .t-rex.conf
|
@@ -756,18 +1417,23 @@ refresh
|
|
756
1417
|
Rcurses::Cursor.hide
|
757
1418
|
begin # Capture main loop
|
758
1419
|
loop do # Main loop
|
759
|
-
@p_inf.say(" #{@mod} Sci=#{@sci} Fix=#{@fix}".i)
|
1420
|
+
@p_inf.say(" #{@xrpn.mode_display} #{@mod} Sci=#{@sci} Fix=#{@fix}".i)
|
760
1421
|
pstack
|
761
1422
|
pregs
|
762
1423
|
@t = @stk.dup
|
1424
|
+
@scroll_action = false
|
763
1425
|
main_getkey("") # Get key input from user
|
764
|
-
help
|
1426
|
+
help unless @scroll_action
|
765
1427
|
@u.push(@t.dup) if @t != @stk and @undo == false
|
766
1428
|
@undo = false
|
767
|
-
|
768
|
-
|
769
|
-
@h
|
770
|
-
|
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
|
771
1437
|
end
|
772
1438
|
end
|
773
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:
|
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-
|
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
|
30
|
-
|
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
|