xrpn 2.5 → 2.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e981e45788657aed96a731cda1c95efc19b3fd91b5e95e5106b116ca3436da8
4
- data.tar.gz: fa62628fef13340908bf22a858fb2f7ba12249cf870d89ee48c7e294aca85c25
3
+ metadata.gz: 0451ad69d90e115070029b6619c12eeea1c3470044080d656b4e7130ff5a7ce2
4
+ data.tar.gz: ac6027e1bf40f409fb5be3fa75ab7dbadecc934ec7a5a3165c6dd82d21b94bd0
5
5
  SHA512:
6
- metadata.gz: 2c0579af23c5ff05cf697a4e2600be32e3e3256f3c9bef8958866d21c312460092317e2a361d736514cfb458162732c17da909582d2f822bbf0a83e5e38c4fd4
7
- data.tar.gz: 888fe711b5e116382e8964bed62a3d15db60da609abf83b9b7f0b9a8242a6ea817f690774af230a392c59a1c27ae39ca3ac9e3ae49bc5728ac587bb36f8e497f
6
+ metadata.gz: 8a67e64e4e2e63fcc712c21d8520a726e753a8978ada1e3c7b56f52cf8611faa63b351df020251ed784ccc402d95ae3413186c20396dcc9325bee966ae548ab6
7
+ data.tar.gz: 6621b4407c422896602022105fc38e159b247ee6856b263fea01366a30c1f1bbeb1951df5c449cc24c999df483b19ff8ae348503fce4cddd4f836e4df1f55ccb
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # XRPN
2
- ![Ruby](https://img.shields.io/badge/language-Ruby-red) [![Gem Version](https://badge.fury.io/rb/xrpn.svg)](https://badge.fury.io/rb/xrpn) ![Unlicense](https://img.shields.io/badge/license-Unlicense-green) ![Stay Amazing](https://img.shields.io/badge/Stay-Amazing-important)
3
2
 
4
- ## Introduction
3
+ ![Ruby](https://img.shields.io/badge/language-Ruby-red) [![Gem Version](https://badge.fury.io/rb/xrpn.svg)](https://badge.fury.io/rb/xrpn) ![Unlicense](https://img.shields.io/badge/license-Unlicense-green) ![Stay Amazing](https://img.shields.io/badge/Stay-Amazing-important)
5
4
 
6
- XRPN is a stack-based programming language, similar to [Forth](https://en.wikipedia.org/wiki/Forth_(programming_language)), but simpler in nature.
5
+ <img src="img/xrpn_logo.svg" align="left" width="150" height="150"> XRPN is a stack-based programming language, similar to [Forth](https://en.wikipedia.org/wiki/Forth_(programming_language)), but simpler in nature.
6
+ <br clear="left"/>
7
7
 
8
8
  XRPN is on-the-fly extensible. Language functions can be upgraded or implemented while programs are running.
9
9
 
@@ -79,9 +79,34 @@ AVIEW
79
79
  END
80
80
  ```
81
81
 
82
+ ## HP-41 RAW File Support
83
+
84
+ XRPN can now inspect HP-41 RAW format program files. Use the `RAWINFO` command to view labels, strings, and hex dump of RAW files.
85
+
86
+ **Usage:**
87
+ ```
88
+ xrpn
89
+ > "/path/to/program.raw"
90
+ > rawinfo
91
+ ```
92
+
93
+ This displays:
94
+ - All program labels (global and local)
95
+ - Text strings found in the program
96
+ - Hex dump for analysis
97
+
98
+ **Note:** Full RAW-to-XRPN conversion requires comprehensive HP-41 bytecode documentation and is planned for future releases. The current implementation provides informational viewing of RAW files.
99
+
82
100
  ## Changelog
83
101
 
84
- ### Version 2.5 (Latest)
102
+ ### Version 2.6 (Latest)
103
+ **New Features and Testing**
104
+
105
+ - **Added HP-41 RAW file viewer** - New `RAWINFO` command displays labels, strings, and hex dump from HP-41 RAW program files
106
+ - **Comprehensive regression test framework** - 58 automated tests across 7 command categories with 100% pass rate
107
+ - **Test infrastructure** - YAML-based test specifications for easy maintenance and extension
108
+
109
+ ### Version 2.5
85
110
  **Critical Bug Fixes and Performance Enhancements**
86
111
 
87
112
  - **Fixed division by zero crashes** - Now properly handles division by zero with error messages instead of crashing
data/tests/README.md ADDED
@@ -0,0 +1,259 @@
1
+ # XRPN Regression Test Framework
2
+
3
+ Comprehensive automated testing suite for XRPN to prevent regressions and ensure all commands work correctly.
4
+
5
+ ## Quick Start
6
+
7
+ Run all tests:
8
+ ```bash
9
+ ruby tests/run_tests.rb
10
+ ```
11
+
12
+ Run specific test category:
13
+ ```bash
14
+ ruby tests/run_tests.rb tests/specs/01_basic_arithmetic.yml
15
+ ```
16
+
17
+ ## Test Framework Architecture
18
+
19
+ ### Components
20
+
21
+ **run_tests.rb** - Main test runner
22
+ - Orchestrates test execution
23
+ - Parses YAML test specifications
24
+ - Validates outputs
25
+ - Reports results
26
+
27
+ **specs/** - Test specification directory
28
+ - YAML files defining test cases
29
+ - Organized by command category
30
+ - Easy to read and extend
31
+
32
+ ### Test Specification Format
33
+
34
+ Tests are defined in YAML files in `specs/` directory:
35
+
36
+ ```yaml
37
+ ---
38
+ - name: "Addition: 5 + 3 = 8"
39
+ commands:
40
+ - "5"
41
+ - "3"
42
+ - "+"
43
+ expected:
44
+ x: 8
45
+
46
+ - name: "With setup"
47
+ setup:
48
+ - "deg" # Setup commands run first
49
+ commands:
50
+ - "30"
51
+ - "sin"
52
+ expected:
53
+ x: 0.5
54
+ ```
55
+
56
+ ### Test Fields
57
+
58
+ **name** (required) - Descriptive test name
59
+
60
+ **commands** (required) - Array of XRPN commands to execute
61
+
62
+ **setup** (optional) - Array of commands to run before test
63
+
64
+ **expected** (required) - Expected results
65
+ - `x`: Expected X register value (number)
66
+ - `tolerance`: Acceptable error margin (default: 0.0001)
67
+ - `contains`: String that should appear in output
68
+
69
+ **should_error** (optional) - Set to `true` if test should produce error
70
+
71
+ ### Example Tests
72
+
73
+ Basic arithmetic:
74
+ ```yaml
75
+ - name: "Square root: sqrt(144) = 12"
76
+ commands:
77
+ - "144"
78
+ - "sqrt"
79
+ expected:
80
+ x: 12
81
+ ```
82
+
83
+ With setup:
84
+ ```yaml
85
+ - name: "sin(30°) = 0.5"
86
+ setup:
87
+ - "deg"
88
+ commands:
89
+ - "30"
90
+ - "sin"
91
+ expected:
92
+ x: 0.5
93
+ ```
94
+
95
+ With tolerance:
96
+ ```yaml
97
+ - name: "Pi approximation"
98
+ commands:
99
+ - "pi"
100
+ expected:
101
+ x: 3.14159
102
+ tolerance: 0.00001
103
+ ```
104
+
105
+ Error handling:
106
+ ```yaml
107
+ - name: "Statistics with empty registers"
108
+ setup:
109
+ - "clrg"
110
+ commands:
111
+ - "mean"
112
+ should_error: true
113
+ ```
114
+
115
+ ## Test Categories
116
+
117
+ Current test suites:
118
+
119
+ 1. **01_basic_arithmetic.yml** - Math operations (+, -, *, /, sqrt, pow, etc.)
120
+ 2. **02_stack_operations.yml** - Stack manipulation (enter, swap, drop, etc.)
121
+ 3. **03_trigonometry.yml** - Trig functions (sin, cos, tan, etc.)
122
+ 4. **04_logarithms.yml** - Log and exponential functions
123
+ 5. **05_registers.yml** - Register operations (sto, rcl, indirect, etc.)
124
+ 6. **06_alpha.yml** - Alpha register and string operations
125
+ 7. **07_statistics.yml** - Statistical functions (mean, sdev, Σ+, etc.)
126
+ 8. **08_conditionals.yml** - Conditional tests (x=0?, x>y?, etc.)
127
+ 9. **09_base_conversion.yml** - Number base conversions
128
+ 10. **10_flags.yml** - Flag operations (sf, cf, fs?, fc?)
129
+
130
+ ## Adding New Tests
131
+
132
+ ### Create New Test Category
133
+
134
+ 1. Create new YAML file in `tests/specs/`:
135
+ ```bash
136
+ vi tests/specs/11_my_category.yml
137
+ ```
138
+
139
+ 2. Add test cases:
140
+ ```yaml
141
+ ---
142
+ # My Category Tests
143
+ - name: "My first test"
144
+ commands:
145
+ - "cmd1"
146
+ - "cmd2"
147
+ expected:
148
+ x: 42
149
+ ```
150
+
151
+ 3. Run tests:
152
+ ```bash
153
+ ruby tests/run_tests.rb
154
+ ```
155
+
156
+ ### Add Test to Existing Category
157
+
158
+ Edit the appropriate YAML file in `tests/specs/` and add your test case following the format above.
159
+
160
+ ## How It Works
161
+
162
+ 1. **Test Execution**
163
+ - Reads YAML test specifications
164
+ - Builds command strings with setup + commands + stdout + off
165
+ - Pipes commands to xrpn: `echo "cmd1,cmd2,stdout,off" | xrpn`
166
+ - Captures first line of output (the stdout result)
167
+
168
+ 2. **Validation**
169
+ - Parses numeric output from stdout
170
+ - Compares against expected value within tolerance
171
+ - Reports pass/fail for each test
172
+
173
+ 3. **Output**
174
+ - Clear pass (✓) / fail (✗) indicators
175
+ - Expected vs actual values for failures
176
+ - Summary with total/passed/failed counts
177
+
178
+ ## Best Practices
179
+
180
+ ### Test Naming
181
+ - Be descriptive: "sin(30°) = 0.5" not "test sin"
182
+ - Include operation and expected result
183
+ - Use standard math notation
184
+
185
+ ### Test Organization
186
+ - Group related commands in same file
187
+ - Use setup for common initialization
188
+ - One test per specific behavior
189
+
190
+ ### Tolerances
191
+ - Default 0.0001 works for most cases
192
+ - Increase for transcendental functions: `tolerance: 0.00001`
193
+ - Use exact match for integers (default tolerance fine)
194
+
195
+ ### Edge Cases
196
+ - Test boundary conditions
197
+ - Test error handling with `should_error: true`
198
+ - Test indirect addressing
199
+ - Test register overflow/underflow
200
+
201
+ ## Extending the Framework
202
+
203
+ ### Custom Validators
204
+
205
+ Edit `run_tests.rb` to add custom validation logic:
206
+
207
+ ```ruby
208
+ def validate_output(output, expected, test)
209
+ # Add custom validation here
210
+ end
211
+ ```
212
+
213
+ ### Output Formats
214
+
215
+ Currently validates X register via stdout.
216
+ Future: Add alpha, register, flag validation.
217
+
218
+ ### Continuous Integration
219
+
220
+ Add to CI pipeline:
221
+ ```bash
222
+ #!/bin/bash
223
+ cd xrpn
224
+ ruby tests/run_tests.rb
225
+ exit $?
226
+ ```
227
+
228
+ ## Troubleshooting
229
+
230
+ **Tests hang/timeout:**
231
+ - Ensure all test programs include implicit "off" (added automatically)
232
+ - Check for infinite loops in test commands
233
+
234
+ **Numeric comparison failures:**
235
+ - Increase tolerance if needed
236
+ - Check European vs US decimal format (handled automatically)
237
+
238
+ **Command not found:**
239
+ - Verify command exists in `/xcmd/` or `/xlib/`
240
+ - Check command spelling in YAML
241
+
242
+ ## Contributing Tests
243
+
244
+ When adding new XRPN features:
245
+
246
+ 1. Write tests FIRST (TDD approach)
247
+ 2. Run tests to see them fail
248
+ 3. Implement feature
249
+ 4. Run tests to see them pass
250
+ 5. Commit both feature and tests together
251
+
252
+ This ensures:
253
+ - Features work as intended
254
+ - Future changes don't break existing functionality
255
+ - Documentation via executable examples
256
+
257
+ ## License
258
+
259
+ Same as XRPN - Public Domain (Unlicense)
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ #
4
+ # XRPN Regression Test Framework
5
+ # Tests all xrpn commands to prevent regressions
6
+ # Usage: ruby tests/run_tests.rb [test_file.yml]
7
+
8
+ require 'yaml'
9
+ require 'json'
10
+ require 'fileutils'
11
+
12
+ class XRPNTestRunner
13
+ attr_reader :passed, :failed, :errors
14
+
15
+ def initialize
16
+ @passed = 0
17
+ @failed = 0
18
+ @errors = 0
19
+ @test_results = []
20
+ @xrpn_bin = File.expand_path('../bin/xrpn', __dir__)
21
+ end
22
+
23
+ def run_all_tests
24
+ test_dir = File.dirname(__FILE__)
25
+ test_files = Dir["#{test_dir}/specs/*.yml"].sort
26
+
27
+ if test_files.empty?
28
+ puts "No test files found in #{test_dir}/specs/"
29
+ puts "Creating test specification directory..."
30
+ FileUtils.mkdir_p("#{test_dir}/specs")
31
+ return
32
+ end
33
+
34
+ puts "=" * 60
35
+ puts "XRPN Regression Test Suite"
36
+ puts "=" * 60
37
+ puts
38
+
39
+ test_files.each do |file|
40
+ run_test_file(file)
41
+ end
42
+
43
+ print_summary
44
+ end
45
+
46
+ def run_test_file(file)
47
+ tests = YAML.load_file(file)
48
+ category = File.basename(file, '.yml')
49
+
50
+ puts "Testing: #{category}"
51
+ puts "-" * 60
52
+
53
+ tests.each do |test|
54
+ run_single_test(test, category)
55
+ end
56
+ puts
57
+ end
58
+
59
+ def run_single_test(test, category)
60
+ name = test['name']
61
+ commands = test['commands']
62
+ expected = test['expected'] || {}
63
+ setup = test['setup'] || []
64
+
65
+ # Build XRPN program as comma-separated commands
66
+ program = build_program(setup, commands, expected)
67
+
68
+ # Write to temp file to avoid shell escaping issues
69
+ temp_file = "/tmp/xrpn_test_#{Process.pid}_#{rand(10000)}.txt"
70
+ File.write(temp_file, program)
71
+
72
+ # Run xrpn by piping commands and capture output
73
+ output = `cat #{temp_file} | #{@xrpn_bin} 2>&1`
74
+ exit_code = $?.exitstatus
75
+
76
+ # Clean up
77
+ File.delete(temp_file) if File.exist?(temp_file)
78
+
79
+ # Parse output and validate
80
+ result = validate_output(output, expected, test)
81
+
82
+ if result[:passed]
83
+ @passed += 1
84
+ print " ✓ #{name}"
85
+ puts " (#{category})"
86
+ else
87
+ @failed += 1
88
+ print " ✗ #{name}"
89
+ puts " (#{category})"
90
+ puts " Expected: #{result[:expected]}"
91
+ puts " Got: #{result[:actual]}"
92
+ puts " Output: #{output.strip}" if test['verbose']
93
+ end
94
+
95
+ rescue => e
96
+ @errors += 1
97
+ puts " ✗ #{name} - ERROR: #{e.message}"
98
+ end
99
+
100
+ def build_program(setup, commands, expected)
101
+ lines = []
102
+
103
+ # Setup phase
104
+ setup.each { |cmd| lines << cmd }
105
+
106
+ # Test commands
107
+ commands.each { |cmd| lines << cmd }
108
+
109
+ # If testing alpha, move alpha to X register before stdout
110
+ lines << "asto x" if expected['alpha']
111
+
112
+ # Output results for validation using stdout
113
+ lines << "stdout" # Output X to stdout for capture
114
+ lines << "off" # Exit cleanly
115
+
116
+ lines.join(",") # Join with commas
117
+ end
118
+
119
+ def validate_output(output, expected, test)
120
+ result = { passed: true, expected: {}, actual: {} }
121
+
122
+ # Check for error conditions
123
+ if test['should_error']
124
+ if output.include?("Error") || output.include?("ERROR") || output.include?("error")
125
+ return result
126
+ else
127
+ result[:passed] = false
128
+ result[:expected] = "Error condition"
129
+ result[:actual] = "No error"
130
+ return result
131
+ end
132
+ end
133
+
134
+ # Parse X register from stdout output (just a number on first line)
135
+ if expected['x']
136
+ # Get first line and convert European decimal format to standard
137
+ first_line = output.split("\n").first.to_s.strip
138
+ clean_output = first_line.gsub(',', '.')
139
+ actual_x = clean_output.to_f rescue nil
140
+ expected_x = expected['x'].to_f
141
+
142
+ tolerance = expected['tolerance'] || 0.0001
143
+ if actual_x.nil? || (actual_x - expected_x).abs > tolerance
144
+ result[:passed] = false
145
+ result[:expected]['x'] = expected_x
146
+ result[:actual]['x'] = actual_x
147
+ end
148
+ end
149
+
150
+ # Parse Alpha register from stdout output (string on first line)
151
+ if expected['alpha']
152
+ first_line = output.split("\n").first.to_s.strip
153
+ actual_alpha = first_line
154
+ expected_alpha = expected['alpha']
155
+
156
+ if actual_alpha != expected_alpha
157
+ result[:passed] = false
158
+ result[:expected]['alpha'] = expected_alpha
159
+ result[:actual]['alpha'] = actual_alpha
160
+ end
161
+ end
162
+
163
+ # Parse string in X register (for commands like dechex that put strings in X)
164
+ if expected['x_string']
165
+ first_line = output.split("\n").first.to_s.strip
166
+ actual_string = first_line
167
+ expected_string = expected['x_string']
168
+
169
+ if actual_string != expected_string
170
+ result[:passed] = false
171
+ result[:expected]['x_string'] = expected_string
172
+ result[:actual]['x_string'] = actual_string
173
+ end
174
+ end
175
+
176
+ # Check for specific output patterns
177
+ if expected['contains']
178
+ unless output.include?(expected['contains'])
179
+ result[:passed] = false
180
+ result[:expected]['output'] = "Contains '#{expected['contains']}'"
181
+ result[:actual]['output'] = "Not found"
182
+ end
183
+ end
184
+
185
+ result
186
+ end
187
+
188
+ def print_summary
189
+ total = @passed + @failed + @errors
190
+
191
+ puts "=" * 60
192
+ puts "Test Results Summary"
193
+ puts "=" * 60
194
+ puts "Total tests: #{total}"
195
+ puts "Passed: #{@passed} ✓"
196
+ puts "Failed: #{@failed} ✗" if @failed > 0
197
+ puts "Errors: #{@errors} ✗" if @errors > 0
198
+ puts
199
+
200
+ if @failed == 0 && @errors == 0
201
+ puts "SUCCESS: All tests passed!"
202
+ exit 0
203
+ else
204
+ puts "FAILURE: #{@failed + @errors} test(s) failed"
205
+ exit 1
206
+ end
207
+ end
208
+ end
209
+
210
+ # Run tests
211
+ if __FILE__ == $0
212
+ runner = XRPNTestRunner.new
213
+
214
+ if ARGV[0]
215
+ runner.run_test_file(ARGV[0])
216
+ runner.print_summary
217
+ else
218
+ runner.run_all_tests
219
+ end
220
+ end
data/xcmd/rawinfo ADDED
@@ -0,0 +1,22 @@
1
+ class XRPN
2
+ # Display information about HP-41 RAW file
3
+ # Usage: "filename.raw" then RAWINFO
4
+ def rawinfo
5
+ filename = @a
6
+ if filename.empty?
7
+ puts "Error: No filename in Alpha register"
8
+ puts "Usage: Put RAW filename in Alpha, then run RAWINFO"
9
+ return
10
+ end
11
+
12
+ # Check file exists
13
+ unless File.exist?(filename)
14
+ puts "Error: File not found: #{filename}"
15
+ return
16
+ end
17
+
18
+ raw_info(filename)
19
+ end
20
+ end
21
+
22
+ # vim:ft=ruby:
data/xlib/_xrpn_version CHANGED
@@ -1,5 +1,5 @@
1
1
  def xrpn_version
2
- puts "XRPN version: 2.5"
2
+ puts "XRPN version: 2.6"
3
3
  puts "RubyGem version: " + Gem.latest_spec_for("xrpn").version.to_s
4
4
  end
5
5
 
data/xlib/raw_info ADDED
@@ -0,0 +1,155 @@
1
+ # HP-41 RAW File Information Viewer
2
+ # Displays labels, strings, and basic info from HP-41 RAW files
3
+
4
+ def raw_info(filename)
5
+ begin
6
+ data = File.binread(filename)
7
+
8
+ puts "=" * 60
9
+ puts "HP-41 RAW File: #{File.basename(filename)}"
10
+ puts "File size: #{data.length} bytes"
11
+ puts "=" * 60
12
+ puts
13
+
14
+ # Extract labels
15
+ labels = []
16
+ strings = []
17
+ pos = 0
18
+
19
+ while pos < data.length
20
+ byte = data[pos].ord
21
+
22
+ # Global label (C0 00 Fx yy LABELNAME)
23
+ if byte == 0xC0 && pos + 3 < data.length && data[pos + 1].ord == 0x00
24
+ label_type = data[pos + 2].ord
25
+ pos += 4
26
+
27
+ # Extract label name
28
+ label = ""
29
+ while pos < data.length && data[pos].ord >= 0x20 && data[pos].ord < 0x7F
30
+ label << data[pos]
31
+ pos += 1
32
+ end
33
+
34
+ type_str = case label_type
35
+ when 0xF4 then "Alpha"
36
+ when 0xF5 then "Numeric"
37
+ else "Unknown"
38
+ end
39
+
40
+ labels << " LBL \"#{label}\" (#{type_str})"
41
+ next
42
+ end
43
+
44
+ # Local label (F6 00 LABELNAME)
45
+ if byte == 0xF6 && pos + 1 < data.length && data[pos + 1].ord == 0x00
46
+ pos += 2
47
+
48
+ # Extract label name
49
+ label = ""
50
+ while pos < data.length && data[pos].ord >= 0x20 && data[pos].ord < 0x7F
51
+ label << data[pos]
52
+ pos += 1
53
+ end
54
+
55
+ labels << " LBL \"#{label}\" (Local)"
56
+ next
57
+ end
58
+
59
+ # Text strings (multiple formats)
60
+ # Format 1: 7F 20 TEXT
61
+ if byte == 0x7F && pos + 1 < data.length && data[pos + 1].ord == 0x20
62
+ pos += 2
63
+ text = ""
64
+ while pos < data.length && data[pos].ord >= 0x20 && data[pos].ord < 0x7F
65
+ text << data[pos]
66
+ pos += 1
67
+ end
68
+ strings << " \"#{text}\"" unless text.empty?
69
+ next
70
+ end
71
+
72
+ # Format 2: FD TEXT (string literals in prompts)
73
+ if byte == 0xFD && pos + 1 < data.length
74
+ pos += 1
75
+ text = ""
76
+ while pos < data.length && data[pos].ord >= 0x20 && data[pos].ord < 0x7F
77
+ text << data[pos]
78
+ pos += 1
79
+ end
80
+ strings << " \"#{text}\"" unless text.empty?
81
+ next
82
+ end
83
+
84
+ # Format 3: F8 TEXT (another string format)
85
+ if byte == 0xF8 && pos + 1 < data.length
86
+ pos += 1
87
+ text = ""
88
+ while pos < data.length && data[pos].ord >= 0x20 && data[pos].ord < 0x7F
89
+ text << data[pos]
90
+ pos += 1
91
+ end
92
+ strings << " \"#{text}\"" unless text.empty?
93
+ next
94
+ end
95
+
96
+ # Format 4: F3 TEXT (yet another string format)
97
+ if byte == 0xF3 && pos + 1 < data.length
98
+ pos += 1
99
+ text = ""
100
+ while pos < data.length && data[pos].ord >= 0x20 && data[pos].ord < 0x7F
101
+ text << data[pos]
102
+ pos += 1
103
+ end
104
+ strings << " \"#{text}\"" unless text.empty?
105
+ next
106
+ end
107
+
108
+ # Format 5: FB TEXT (text string marker)
109
+ if byte == 0xFB && pos + 1 < data.length
110
+ pos += 1
111
+ text = ""
112
+ while pos < data.length && data[pos].ord >= 0x20 && data[pos].ord < 0x7F
113
+ text << data[pos]
114
+ pos += 1
115
+ end
116
+ strings << " \"#{text}\"" unless text.empty?
117
+ next
118
+ end
119
+
120
+ pos += 1
121
+ end
122
+
123
+ # Display findings
124
+ puts "Labels found (#{labels.length}):"
125
+ if labels.empty?
126
+ puts " (none)"
127
+ else
128
+ labels.each { |l| puts l }
129
+ end
130
+ puts
131
+
132
+ puts "Text strings found (#{strings.length}):"
133
+ if strings.empty?
134
+ puts " (none)"
135
+ else
136
+ strings.each { |s| puts s }
137
+ end
138
+ puts
139
+
140
+ puts "Hex dump (first 256 bytes):"
141
+ puts "-" * 60
142
+ system("hexdump -C '#{filename}' | head -20")
143
+ puts
144
+
145
+ puts "Note: Full RAW to XRPN conversion requires comprehensive"
146
+ puts "HP-41 bytecode documentation. This viewer extracts readable"
147
+ puts "labels and strings for informational purposes."
148
+ puts "=" * 60
149
+
150
+ rescue => e
151
+ puts "Error reading RAW file: #{e.message}"
152
+ end
153
+ end
154
+
155
+ # vim:ft=ruby:
data/xrpn.gemspec CHANGED
@@ -1,9 +1,9 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'xrpn'
3
- s.version = '2.5'
3
+ s.version = '2.6'
4
4
  s.licenses = ['Unlicense']
5
5
  s.summary = "XRPN - The eXtended RPN (Reverse Polish Notation) programming language"
6
- s.description = "A full programming language and environment extending the features of the venerable HP calculator programmable calculators. With XRPN you can accomplish a wide range of modern programming tasks as well as running existing HP-41 FOCAL programs directly. XRPN gives modern life to tens of thousands of old HP calculator programs and opens the possibilities to many, many more. New in 2.5: Critical bug fixes and performance enhancements - fixed division by zero crashes, nil reference errors, memory leaks, and optimized file system operations."
6
+ s.description = "A full programming language and environment extending the features of the venerable HP calculator programmable calculators. With XRPN you can accomplish a wide range of modern programming tasks as well as running existing HP-41 FOCAL programs directly. XRPN gives modern life to tens of thousands of old HP calculator programs and opens the possibilities to many, many more. New in 2.6: Comprehensive regression test framework with 58 automated tests and HP-41 RAW file viewer for inspecting thousands of legacy HP-41 programs."
7
7
 
8
8
  s.authors = ["Geir Isene"]
9
9
  s.email = 'g@isene.com'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xrpn
3
3
  version: !ruby/object:Gem::Version
4
- version: '2.5'
4
+ version: '2.6'
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-07-05 00:00:00.000000000 Z
11
+ date: 2025-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tty-prompt
@@ -28,9 +28,9 @@ description: 'A full programming language and environment extending the features
28
28
  the venerable HP calculator programmable calculators. With XRPN you can accomplish
29
29
  a wide range of modern programming tasks as well as running existing HP-41 FOCAL
30
30
  programs directly. XRPN gives modern life to tens of thousands of old HP calculator
31
- programs and opens the possibilities to many, many more. New in 2.5: Critical bug
32
- fixes and performance enhancements - fixed division by zero crashes, nil reference
33
- errors, memory leaks, and optimized file system operations.'
31
+ programs and opens the possibilities to many, many more. New in 2.6: Comprehensive
32
+ regression test framework with 58 automated tests and HP-41 RAW file viewer for
33
+ inspecting thousands of legacy HP-41 programs.'
34
34
  email: g@isene.com
35
35
  executables:
36
36
  - xrpn
@@ -40,7 +40,9 @@ files:
40
40
  - README.md
41
41
  - bin/xrpn
42
42
  - conf
43
+ - tests/README.md
43
44
  - tests/final_test.xrpn
45
+ - tests/run_tests.rb
44
46
  - tests/test_bugs.xrpn
45
47
  - tests/test_bugs_simple.xrpn
46
48
  - tests/test_final.xrpn
@@ -226,6 +228,7 @@ files:
226
228
  - xcmd/r_p
227
229
  - xcmd/rad
228
230
  - xcmd/rand
231
+ - xcmd/rawinfo
229
232
  - xcmd/rcl
230
233
  - xcmd/rclaf
231
234
  - xcmd/rclflag
@@ -334,6 +337,7 @@ files:
334
337
  - xlib/locate_prg
335
338
  - xlib/numeric
336
339
  - xlib/numformat
340
+ - xlib/raw_info
337
341
  - xlib/read_state
338
342
  - xlib/read_xcmd
339
343
  - xlib/rxcmd