xrpn 2.4 → 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 +4 -4
- data/README.md +47 -3
- data/bin/xrpn +5 -3
- data/tests/README.md +259 -0
- data/tests/final_test.xrpn +44 -0
- data/tests/run_tests.rb +220 -0
- data/tests/test_bugs.xrpn +148 -0
- data/tests/test_bugs_simple.xrpn +121 -0
- data/tests/test_final.xrpn +4 -0
- data/tests/test_simple.xrpn +35 -0
- data/xcmd/divide +6 -2
- data/xcmd/getfile +1 -1
- data/xcmd/mean +12 -2
- data/xcmd/rawinfo +22 -0
- data/xlib/_xrpn_version +1 -1
- data/xlib/debug_mode +2 -1
- data/xlib/ind +2 -1
- data/xlib/numeric +6 -5
- data/xlib/raw_info +155 -0
- data/xrpn.gemspec +3 -3
- metadata +14 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0451ad69d90e115070029b6619c12eeea1c3470044080d656b4e7130ff5a7ce2
|
|
4
|
+
data.tar.gz: ac6027e1bf40f409fb5be3fa75ab7dbadecc934ec7a5a3165c6dd82d21b94bd0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8a67e64e4e2e63fcc712c21d8520a726e753a8978ada1e3c7b56f52cf8611faa63b351df020251ed784ccc402d95ae3413186c20396dcc9325bee966ae548ab6
|
|
7
|
+
data.tar.gz: 6621b4407c422896602022105fc38e159b247ee6856b263fea01366a30c1f1bbeb1951df5c449cc24c999df483b19ff8ae348503fce4cddd4f836e4df1f55ccb
|
data/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# XRPN
|
|
2
|
-
 [](https://badge.fury.io/rb/xrpn)  
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
 [](https://badge.fury.io/rb/xrpn)  
|
|
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,6 +79,50 @@ 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
|
+
|
|
100
|
+
## Changelog
|
|
101
|
+
|
|
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
|
|
110
|
+
**Critical Bug Fixes and Performance Enhancements**
|
|
111
|
+
|
|
112
|
+
- **Fixed division by zero crashes** - Now properly handles division by zero with error messages instead of crashing
|
|
113
|
+
- **Fixed nil reference errors in statistics** - Statistics commands (like `mean`) now handle empty or uninitialized registers gracefully
|
|
114
|
+
- **Fixed variable name error in file operations** - Corrected undefined variable reference in `getfile` command
|
|
115
|
+
- **Fixed indirect addressing nil references** - Indirect addressing now handles nil register values safely
|
|
116
|
+
- **Optimized file system operations** - Improved startup performance by using `FileUtils.mkdir_p` for directory creation
|
|
117
|
+
- **Fixed memory leak** - TTY::Prompt is now lazily initialized only when debug mode is actually used
|
|
118
|
+
- **Optimized numeric formatting** - Eliminated redundant string conversions in rounding overflow calculations
|
|
119
|
+
- **Added comprehensive test suite** - New test programs in `/tests/` directory to verify bug fixes and prevent regressions
|
|
120
|
+
|
|
121
|
+
This release significantly improves the stability and performance of XRPN while maintaining full backward compatibility.
|
|
122
|
+
|
|
123
|
+
### Version 2.4
|
|
124
|
+
- Fixed loading of files via the -f switch
|
|
125
|
+
|
|
82
126
|
## Documentation
|
|
83
127
|
|
|
84
128
|
...is all in [the wiki page in this repo](https://github.com/isene/xrpn/wiki/XRPN-Documentation).
|
data/bin/xrpn
CHANGED
|
@@ -27,9 +27,11 @@ require 'timeout'
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
# ENSURE DIRs
|
|
30
|
-
|
|
30
|
+
require 'fileutils'
|
|
31
|
+
home_xrpn = File.join(Dir.home, ".xrpn")
|
|
32
|
+
FileUtils.mkdir_p(home_xrpn)
|
|
31
33
|
["data", "extra", "print", "xcmd", "xlib"].each do |d|
|
|
32
|
-
|
|
34
|
+
FileUtils.mkdir_p(File.join(home_xrpn, d))
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
# READ LIBRARY AND COMMANDS
|
|
@@ -102,7 +104,7 @@ load(Dir.home+'/.xrpn/theme') if File.exist?(Dir.home+'/.xrpn/theme') # Override
|
|
|
102
104
|
# THE CORE OF THE RPN PROGRAM
|
|
103
105
|
@nl = false # Nolift set to false
|
|
104
106
|
@i = 0 # Counter to prevent infinite loops
|
|
105
|
-
@debugprompt =
|
|
107
|
+
@debugprompt = nil
|
|
106
108
|
until @p.pc == @p.prg[@p.pg].length do
|
|
107
109
|
# Enter debug mode if $debug or $prompt is set, else read next program line
|
|
108
110
|
($debug or $prompt) ? debug_mode : @line = @p.prg[@p.pg][@p.pc]
|
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,44 @@
|
|
|
1
|
+
LBL "FINAL"
|
|
2
|
+
"Final bug test"
|
|
3
|
+
aview
|
|
4
|
+
PSE
|
|
5
|
+
|
|
6
|
+
"Testing division..."
|
|
7
|
+
aview
|
|
8
|
+
10
|
|
9
|
+
2
|
|
10
|
+
/
|
|
11
|
+
"10/2 = "
|
|
12
|
+
arcl x
|
|
13
|
+
aview
|
|
14
|
+
PSE
|
|
15
|
+
|
|
16
|
+
"Testing stats..."
|
|
17
|
+
aview
|
|
18
|
+
clrg
|
|
19
|
+
1
|
|
20
|
+
2
|
|
21
|
+
Σ+
|
|
22
|
+
3
|
|
23
|
+
4
|
|
24
|
+
Σ+
|
|
25
|
+
mean
|
|
26
|
+
"Mean: "
|
|
27
|
+
arcl x
|
|
28
|
+
aview
|
|
29
|
+
PSE
|
|
30
|
+
|
|
31
|
+
"Testing file error..."
|
|
32
|
+
aview
|
|
33
|
+
"nothere.txt"
|
|
34
|
+
asto 01
|
|
35
|
+
rcl 01
|
|
36
|
+
getfile
|
|
37
|
+
"File result: "
|
|
38
|
+
arcl x
|
|
39
|
+
aview
|
|
40
|
+
PSE
|
|
41
|
+
|
|
42
|
+
"All tests passed!"
|
|
43
|
+
aview
|
|
44
|
+
end
|
data/tests/run_tests.rb
ADDED
|
@@ -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
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# XRPN Test Program for Bug Fixes and Performance
|
|
2
|
+
# Tests critical bugs and performance issues
|
|
3
|
+
|
|
4
|
+
LBL "TESTALL"
|
|
5
|
+
"=== XRPN Bug Test ==="
|
|
6
|
+
aview
|
|
7
|
+
PSE
|
|
8
|
+
|
|
9
|
+
# Test 1: Division by zero
|
|
10
|
+
LBL "TEST1"
|
|
11
|
+
"Test 1: Division/0"
|
|
12
|
+
aview
|
|
13
|
+
PSE
|
|
14
|
+
5
|
|
15
|
+
0
|
|
16
|
+
/
|
|
17
|
+
"DIV/0 Failed"
|
|
18
|
+
aview
|
|
19
|
+
stop
|
|
20
|
+
LBL 01
|
|
21
|
+
"DIV/0 Caught OK"
|
|
22
|
+
aview
|
|
23
|
+
PSE
|
|
24
|
+
|
|
25
|
+
# Test 2: Statistics with empty registers
|
|
26
|
+
LBL "TEST2"
|
|
27
|
+
"Test 2: Stats/empty"
|
|
28
|
+
aview
|
|
29
|
+
PSE
|
|
30
|
+
clrg
|
|
31
|
+
mean
|
|
32
|
+
"Stats Failed"
|
|
33
|
+
aview
|
|
34
|
+
stop
|
|
35
|
+
LBL 02
|
|
36
|
+
"Stats Caught OK"
|
|
37
|
+
aview
|
|
38
|
+
PSE
|
|
39
|
+
|
|
40
|
+
# Test 3: File operations with non-existent file
|
|
41
|
+
LBL "TEST3"
|
|
42
|
+
"Test 3: Bad file"
|
|
43
|
+
aview
|
|
44
|
+
PSE
|
|
45
|
+
"nonexistent_file_12345.txt"
|
|
46
|
+
asto 01
|
|
47
|
+
rcl 01
|
|
48
|
+
getfile
|
|
49
|
+
"File err Failed"
|
|
50
|
+
aview
|
|
51
|
+
stop
|
|
52
|
+
LBL 03
|
|
53
|
+
"File err OK"
|
|
54
|
+
aview
|
|
55
|
+
PSE
|
|
56
|
+
|
|
57
|
+
# Test 4: Indirect addressing with nil register
|
|
58
|
+
LBL "TEST4"
|
|
59
|
+
"Test 4: IND nil"
|
|
60
|
+
aview
|
|
61
|
+
PSE
|
|
62
|
+
clrg
|
|
63
|
+
100
|
|
64
|
+
sto 50
|
|
65
|
+
# Register 51 is nil/empty
|
|
66
|
+
51
|
|
67
|
+
sto ind x
|
|
68
|
+
"IND nil Failed"
|
|
69
|
+
aview
|
|
70
|
+
stop
|
|
71
|
+
LBL 04
|
|
72
|
+
"IND nil OK"
|
|
73
|
+
aview
|
|
74
|
+
PSE
|
|
75
|
+
|
|
76
|
+
# Test 5: Numeric rounding overflow
|
|
77
|
+
LBL "TEST5"
|
|
78
|
+
"Test 5: Round ovfl"
|
|
79
|
+
aview
|
|
80
|
+
PSE
|
|
81
|
+
9.9999999999
|
|
82
|
+
fix 2
|
|
83
|
+
# This should round to 10.00
|
|
84
|
+
"Rounding: "
|
|
85
|
+
arcl x
|
|
86
|
+
aview
|
|
87
|
+
PSE
|
|
88
|
+
10
|
|
89
|
+
x!=y?
|
|
90
|
+
gto 05
|
|
91
|
+
"Round OK"
|
|
92
|
+
aview
|
|
93
|
+
PSE
|
|
94
|
+
gto "TEST6"
|
|
95
|
+
LBL 05
|
|
96
|
+
"Round Failed"
|
|
97
|
+
aview
|
|
98
|
+
stop
|
|
99
|
+
|
|
100
|
+
# Test 6: Statistics with data
|
|
101
|
+
LBL "TEST6"
|
|
102
|
+
"Test 6: Stats calc"
|
|
103
|
+
aview
|
|
104
|
+
PSE
|
|
105
|
+
clrg
|
|
106
|
+
1
|
|
107
|
+
enter
|
|
108
|
+
2
|
|
109
|
+
Σ+
|
|
110
|
+
3
|
|
111
|
+
enter
|
|
112
|
+
4
|
|
113
|
+
Σ+
|
|
114
|
+
5
|
|
115
|
+
enter
|
|
116
|
+
6
|
|
117
|
+
Σ+
|
|
118
|
+
mean
|
|
119
|
+
"Mean Y="
|
|
120
|
+
arcl y
|
|
121
|
+
" X="
|
|
122
|
+
arcl x
|
|
123
|
+
aview
|
|
124
|
+
PSE
|
|
125
|
+
# Should be Y=2, X=4
|
|
126
|
+
2
|
|
127
|
+
x!=y?
|
|
128
|
+
gto 06
|
|
129
|
+
rdwn
|
|
130
|
+
4
|
|
131
|
+
x!=y?
|
|
132
|
+
gto 06
|
|
133
|
+
"Stats calc OK"
|
|
134
|
+
aview
|
|
135
|
+
PSE
|
|
136
|
+
gto "DONE"
|
|
137
|
+
LBL 06
|
|
138
|
+
"Stats calc Failed"
|
|
139
|
+
aview
|
|
140
|
+
stop
|
|
141
|
+
|
|
142
|
+
LBL "DONE"
|
|
143
|
+
"=== All Tests Done ==="
|
|
144
|
+
aview
|
|
145
|
+
PSE
|
|
146
|
+
"Check for errors"
|
|
147
|
+
aview
|
|
148
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
LBL "TESTALL"
|
|
2
|
+
"XRPN Bug Test"
|
|
3
|
+
aview
|
|
4
|
+
PSE
|
|
5
|
+
xeq "TEST1"
|
|
6
|
+
xeq "TEST2"
|
|
7
|
+
xeq "TEST3"
|
|
8
|
+
xeq "TEST4"
|
|
9
|
+
xeq "TEST5"
|
|
10
|
+
xeq "TEST6"
|
|
11
|
+
"All Tests Done"
|
|
12
|
+
aview
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
LBL "TEST1"
|
|
16
|
+
"Test 1: Division/0"
|
|
17
|
+
aview
|
|
18
|
+
PSE
|
|
19
|
+
5
|
|
20
|
+
0
|
|
21
|
+
/
|
|
22
|
+
"DIV/0 Failed"
|
|
23
|
+
aview
|
|
24
|
+
stop
|
|
25
|
+
|
|
26
|
+
LBL "TEST2"
|
|
27
|
+
"Test 2: Stats/empty"
|
|
28
|
+
aview
|
|
29
|
+
PSE
|
|
30
|
+
clrg
|
|
31
|
+
mean
|
|
32
|
+
"Stats Failed"
|
|
33
|
+
aview
|
|
34
|
+
stop
|
|
35
|
+
|
|
36
|
+
LBL "TEST3"
|
|
37
|
+
"Test 3: Bad file"
|
|
38
|
+
aview
|
|
39
|
+
PSE
|
|
40
|
+
"nonexistent_file_12345.txt"
|
|
41
|
+
asto 01
|
|
42
|
+
rcl 01
|
|
43
|
+
getfile
|
|
44
|
+
"File err Failed"
|
|
45
|
+
aview
|
|
46
|
+
stop
|
|
47
|
+
|
|
48
|
+
LBL "TEST4"
|
|
49
|
+
"Test 4: IND nil"
|
|
50
|
+
aview
|
|
51
|
+
PSE
|
|
52
|
+
clrg
|
|
53
|
+
100
|
|
54
|
+
sto 50
|
|
55
|
+
51
|
|
56
|
+
sto ind x
|
|
57
|
+
"IND nil Failed"
|
|
58
|
+
aview
|
|
59
|
+
stop
|
|
60
|
+
|
|
61
|
+
LBL "TEST5"
|
|
62
|
+
"Test 5: Round ovfl"
|
|
63
|
+
aview
|
|
64
|
+
PSE
|
|
65
|
+
9.9999999999
|
|
66
|
+
fix 2
|
|
67
|
+
"Rounding: "
|
|
68
|
+
arcl x
|
|
69
|
+
aview
|
|
70
|
+
PSE
|
|
71
|
+
10
|
|
72
|
+
x!=y?
|
|
73
|
+
gto 05
|
|
74
|
+
"Round OK"
|
|
75
|
+
aview
|
|
76
|
+
PSE
|
|
77
|
+
rtn
|
|
78
|
+
LBL 05
|
|
79
|
+
"Round Failed"
|
|
80
|
+
aview
|
|
81
|
+
stop
|
|
82
|
+
|
|
83
|
+
LBL "TEST6"
|
|
84
|
+
"Test 6: Stats calc"
|
|
85
|
+
aview
|
|
86
|
+
PSE
|
|
87
|
+
clrg
|
|
88
|
+
1
|
|
89
|
+
enter
|
|
90
|
+
2
|
|
91
|
+
Σ+
|
|
92
|
+
3
|
|
93
|
+
enter
|
|
94
|
+
4
|
|
95
|
+
Σ+
|
|
96
|
+
5
|
|
97
|
+
enter
|
|
98
|
+
6
|
|
99
|
+
Σ+
|
|
100
|
+
mean
|
|
101
|
+
"Mean Y="
|
|
102
|
+
arcl y
|
|
103
|
+
" X="
|
|
104
|
+
arcl x
|
|
105
|
+
aview
|
|
106
|
+
PSE
|
|
107
|
+
2
|
|
108
|
+
x!=y?
|
|
109
|
+
gto 06
|
|
110
|
+
rdwn
|
|
111
|
+
4
|
|
112
|
+
x!=y?
|
|
113
|
+
gto 06
|
|
114
|
+
"Stats calc OK"
|
|
115
|
+
aview
|
|
116
|
+
PSE
|
|
117
|
+
rtn
|
|
118
|
+
LBL 06
|
|
119
|
+
"Stats calc Failed"
|
|
120
|
+
aview
|
|
121
|
+
stop
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
LBL "MAIN"
|
|
2
|
+
"Starting tests"
|
|
3
|
+
aview
|
|
4
|
+
PSE
|
|
5
|
+
|
|
6
|
+
"Test div by zero"
|
|
7
|
+
aview
|
|
8
|
+
5
|
|
9
|
+
0
|
|
10
|
+
/
|
|
11
|
+
"DIV OK"
|
|
12
|
+
aview
|
|
13
|
+
PSE
|
|
14
|
+
|
|
15
|
+
"Test stats empty"
|
|
16
|
+
aview
|
|
17
|
+
clrg
|
|
18
|
+
mean
|
|
19
|
+
"STATS OK"
|
|
20
|
+
aview
|
|
21
|
+
PSE
|
|
22
|
+
|
|
23
|
+
"Test bad file"
|
|
24
|
+
aview
|
|
25
|
+
"nonexistent.txt"
|
|
26
|
+
asto 01
|
|
27
|
+
rcl 01
|
|
28
|
+
getfile
|
|
29
|
+
"FILE OK"
|
|
30
|
+
aview
|
|
31
|
+
PSE
|
|
32
|
+
|
|
33
|
+
"All tests done"
|
|
34
|
+
aview
|
|
35
|
+
end
|
data/xcmd/divide
CHANGED
data/xcmd/getfile
CHANGED
data/xcmd/mean
CHANGED
|
@@ -4,8 +4,18 @@ class XRPN
|
|
|
4
4
|
@l = @x
|
|
5
5
|
lift
|
|
6
6
|
lift
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
# Check for nil registers and zero divisor
|
|
8
|
+
sum_x = @reg[@srg] || 0
|
|
9
|
+
sum_y = @reg[@srg + 2] || 0
|
|
10
|
+
count = @reg[@srg + 5] || 0
|
|
11
|
+
|
|
12
|
+
if count == 0
|
|
13
|
+
@x = 0
|
|
14
|
+
@y = 0
|
|
15
|
+
else
|
|
16
|
+
@x = sum_x / count
|
|
17
|
+
@y = sum_y / count
|
|
18
|
+
end
|
|
9
19
|
end
|
|
10
20
|
end
|
|
11
21
|
|
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
data/xlib/debug_mode
CHANGED
data/xlib/ind
CHANGED
data/xlib/numeric
CHANGED
|
@@ -36,12 +36,13 @@ class Numeric
|
|
|
36
36
|
s = x.to_i.to_s
|
|
37
37
|
self < 0 ? m = "-" : m = ""
|
|
38
38
|
s.sub!(/-/, '')
|
|
39
|
-
|
|
40
|
-
if
|
|
41
|
-
s = (x + 1).
|
|
42
|
-
f =
|
|
39
|
+
f_val = (x.frc * 10 ** i).round
|
|
40
|
+
if f_val >= 10 ** i # Fix rounding overflow
|
|
41
|
+
s = (x.to_i + 1).to_s
|
|
42
|
+
f = "0" * i
|
|
43
|
+
else
|
|
44
|
+
f = f_val.to_s.rjust(i, '0')
|
|
43
45
|
end
|
|
44
|
-
f = f.to_s
|
|
45
46
|
i > 0 ? s.sub!(/(\d*\.)(\d{,#{i}}).*/, '\1\2') : s.sub!(/(\d*).*/, '\1')
|
|
46
47
|
if e.abs >= n # If exponent kicks in
|
|
47
48
|
s += "." + f.ljust(i, "0")
|
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.
|
|
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.
|
|
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'
|
|
@@ -11,6 +11,6 @@ Gem::Specification.new do |s|
|
|
|
11
11
|
|
|
12
12
|
s.add_runtime_dependency 'tty-prompt', '~> 0.23'
|
|
13
13
|
|
|
14
|
-
s.files = ["bin/xrpn", "conf", "theme_example", "README.md", "xrpn.gemspec"] + Dir.glob("xcmd/*") + Dir.glob("xlib/*")
|
|
14
|
+
s.files = ["bin/xrpn", "conf", "theme_example", "README.md", "xrpn.gemspec"] + Dir.glob("xcmd/*") + Dir.glob("xlib/*") + Dir.glob("tests/*")
|
|
15
15
|
s.executables << 'xrpn'
|
|
16
16
|
end
|
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.
|
|
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-
|
|
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,8 +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.
|
|
32
|
-
|
|
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.'
|
|
33
34
|
email: g@isene.com
|
|
34
35
|
executables:
|
|
35
36
|
- xrpn
|
|
@@ -39,6 +40,13 @@ files:
|
|
|
39
40
|
- README.md
|
|
40
41
|
- bin/xrpn
|
|
41
42
|
- conf
|
|
43
|
+
- tests/README.md
|
|
44
|
+
- tests/final_test.xrpn
|
|
45
|
+
- tests/run_tests.rb
|
|
46
|
+
- tests/test_bugs.xrpn
|
|
47
|
+
- tests/test_bugs_simple.xrpn
|
|
48
|
+
- tests/test_final.xrpn
|
|
49
|
+
- tests/test_simple.xrpn
|
|
42
50
|
- theme_example
|
|
43
51
|
- xcmd/abs
|
|
44
52
|
- xcmd/acos
|
|
@@ -220,6 +228,7 @@ files:
|
|
|
220
228
|
- xcmd/r_p
|
|
221
229
|
- xcmd/rad
|
|
222
230
|
- xcmd/rand
|
|
231
|
+
- xcmd/rawinfo
|
|
223
232
|
- xcmd/rcl
|
|
224
233
|
- xcmd/rclaf
|
|
225
234
|
- xcmd/rclflag
|
|
@@ -328,6 +337,7 @@ files:
|
|
|
328
337
|
- xlib/locate_prg
|
|
329
338
|
- xlib/numeric
|
|
330
339
|
- xlib/numformat
|
|
340
|
+
- xlib/raw_info
|
|
331
341
|
- xlib/read_state
|
|
332
342
|
- xlib/read_xcmd
|
|
333
343
|
- xlib/rxcmd
|