xrpn 2.5 → 2.7

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.
data/xlib/raw_encoder ADDED
@@ -0,0 +1,258 @@
1
+ # HP-41 RAW File Encoder
2
+ # Converts XRPN text format to RAW bytecode
3
+
4
+ def raw_encode(program, label_name = "PROG")
5
+ # Command to opcode mapping (inverse of decoder) - Phase 1 + 2
6
+ commands = {
7
+ # Arithmetic
8
+ "+" => [0x81],
9
+ "-" => [0x82],
10
+ "*" => [0x83],
11
+ "/" => [0x84],
12
+ "END" => [0x85],
13
+ "SQRT" => [0x86],
14
+ "CLST" => [0x87],
15
+
16
+ # Math functions (Phase 4) - Core ROM single-byte opcodes
17
+ # Safe to encode - will appear after operations/numbers (non-ASCII context)
18
+ "LN" => [0x50],
19
+ "X^2" => [0x51],
20
+ "POW" => [0x53],
21
+ "E^X" => [0x55],
22
+ "LOG" => [0x56],
23
+ "10^X" => [0x57],
24
+ "E^X-1" => [0x58],
25
+ "SIN" => [0x59],
26
+ "COS" => [0x5A],
27
+ "TAN" => [0x5B],
28
+ "ASIN" => [0x5C],
29
+ "ACOS" => [0x5D],
30
+ "ATAN" => [0x5E],
31
+ "1/X" => [0x60],
32
+ "ABS" => [0x61],
33
+ "LN1+X" => [0x65],
34
+ "LN1X" => [0x65], # XRPN alias
35
+
36
+ # Stack operations
37
+ "ENTER" => [0x88],
38
+ "SWAP" => [0x89],
39
+ "XY" => [0x89], # Alias for SWAP
40
+ "RDN" => [0x8A],
41
+ "CHS" => [0x8B],
42
+ "LASTX" => [0x8C],
43
+ "CLX" => [0x8D],
44
+
45
+ # Display/IO
46
+ "CLA" => [0x80],
47
+ "PROMPT" => [0x8E],
48
+ "RTN" => [0x8F],
49
+ "AVIEW" => [0x9B, 0x73],
50
+ "VIEW" => [0x9A, 0x72],
51
+ "PSE" => [0x9C],
52
+ "BEEP" => [0x9D],
53
+ "STOP" => [0x9E],
54
+
55
+ # Extended operations (Phase 2)
56
+ "X<>" => [0x92],
57
+ "DEG" => [0x93],
58
+ "RAD" => [0x94],
59
+ "GRAD" => [0x95],
60
+ "FIX" => [0x96],
61
+ "SCI" => [0x97],
62
+ "ENG" => [0x98],
63
+ "DSE" => [0x9A],
64
+ "ISG" => [0x9F],
65
+
66
+ # Additional
67
+ "ASTO" => [0xCE],
68
+ "~" => [0x7E], # Tilde separator
69
+ }
70
+
71
+ result = []
72
+ main_label_written = false
73
+
74
+ program.each do |line|
75
+ line = line.strip
76
+ next if line.empty?
77
+ next if line.start_with?("#") # Skip comments
78
+
79
+ # Label definition
80
+ if line =~ /^LBL\s+"(.+)"$/i
81
+ lbl_name = $1
82
+ if !main_label_written
83
+ # First label is global (main entry point)
84
+ result += [0xC0, 0x00, 0xF7, 0x00] # Global label header
85
+ result += lbl_name.bytes
86
+ # No terminator needed - next opcode (non-ASCII) terminates naturally
87
+ main_label_written = true
88
+ else
89
+ # Subsequent labels are local
90
+ result += [0xF6, 0x00]
91
+ result += lbl_name.bytes
92
+ # No terminator needed
93
+ end
94
+ next
95
+ end
96
+
97
+ # String literals
98
+ if line =~ /^"(.+)"$/
99
+ text = $1
100
+ result << 0xFD
101
+ result += text.bytes
102
+ next
103
+ end
104
+
105
+ # RCL register - Always use two-byte format to avoid label conflicts
106
+ if line =~ /^RCL\s+(\d+)$/i
107
+ reg = $1.to_i
108
+ result += [0xA7, reg]
109
+ next
110
+ end
111
+
112
+ # STO register - Always use two-byte format to avoid label conflicts
113
+ if line =~ /^STO\s+(\d+)$/i
114
+ reg = $1.to_i
115
+ result += [0xB2, reg]
116
+ next
117
+ end
118
+
119
+ # GTO with label
120
+ if line =~ /^GTO\s+"(.+)"$/i
121
+ label = $1
122
+ result << 0x90
123
+ result += label.bytes
124
+ next
125
+ end
126
+
127
+ # XEQ with label
128
+ if line =~ /^XEQ\s+"(.+)"$/i
129
+ label = $1
130
+ result << 0x91
131
+ result += label.bytes
132
+ next
133
+ end
134
+
135
+ # Conditionals
136
+ conditionals = {
137
+ "X=0?" => 0x67,
138
+ "X!=0?" => 0x68,
139
+ "X>0?" => 0x69,
140
+ "X<0?" => 0x6A,
141
+ "X>=0?" => 0x6B,
142
+ "X<=0?" => 0x6C,
143
+ "X=Y?" => 0x71,
144
+ "X!=Y?" => 0x72,
145
+ "X>Y?" => 0x73,
146
+ "X<Y?" => 0x74,
147
+ "X>=Y?" => 0x75,
148
+ "X<=Y?" => 0x76,
149
+ "X<>Y?" => 0x78,
150
+ }
151
+ cmd_upper = line.upcase
152
+ if conditionals[cmd_upper]
153
+ result << conditionals[cmd_upper]
154
+ next
155
+ end
156
+
157
+ # Flag operations
158
+ if line =~ /^(SF|CF|FS\?|FC\?|FS\?C|FC\?C|FSC\?)\s+(\d+)$/i
159
+ op = $1.upcase
160
+ flag = $2.to_i
161
+ flag_ops = {
162
+ "SF" => 0xA8,
163
+ "CF" => 0xA9,
164
+ "FS?" => 0xAA,
165
+ "FC?" => 0xAB,
166
+ "FS?C" => 0xAC,
167
+ "FC?C" => 0xAD,
168
+ "FSC?" => 0xAE,
169
+ }
170
+ if flag_ops[op]
171
+ result += [flag_ops[op], flag]
172
+ next
173
+ end
174
+ end
175
+
176
+ # Numeric literals - Use F1 XX format to avoid label conflicts
177
+ if line =~ /^([0-9])$/
178
+ digit = $1.to_i
179
+ result += [0xF1, 0x30 + digit]
180
+ next
181
+ end
182
+
183
+ # Multi-digit numbers - NOT SAFELY ENCODABLE
184
+ # Problem: 0x00-0x7E would corrupt labels
185
+ # HP-41 uses these ONLY in program body after non-ASCII bytes
186
+ # For export: Use digit-by-digit entry or register storage
187
+ if line =~ /^([1-9][0-9]+)$/
188
+ num = $1
189
+ puts "Warning: Multi-digit number #{num} - enter digit-by-digit or use RCL"
190
+ next
191
+ end
192
+
193
+ # XROM extended functions (E0 00 XX)
194
+ xrom_funcs = {
195
+ "ANUM" => [0xE0, 0x00, 0x00],
196
+ "ALENG" => [0xE0, 0x00, 0x01],
197
+ "APPCHR" => [0xE0, 0x00, 0x02],
198
+ "APPREC" => [0xE0, 0x00, 0x03],
199
+ "ARCLREC" => [0xE0, 0x00, 0x04],
200
+ "AROT" => [0xE0, 0x00, 0x05],
201
+ "ATOX" => [0xE0, 0x00, 0x06],
202
+ "CLFL" => [0xE0, 0x00, 0x07],
203
+ "CLKEYS" => [0xE0, 0x00, 0x08],
204
+ "CRFLAS" => [0xE0, 0x00, 0x09],
205
+ "CRFLD" => [0xE0, 0x00, 0x0A],
206
+ "DELCHR" => [0xE0, 0x00, 0x0B],
207
+ "DELREC" => [0xE0, 0x00, 0x0C],
208
+ "EMDIR" => [0xE0, 0x00, 0x0D],
209
+ "FLSIZE" => [0xE0, 0x00, 0x0E],
210
+ "GETAS" => [0xE0, 0x00, 0x0F],
211
+ "GETKEY" => [0xE0, 0x00, 0x10],
212
+ "GETP" => [0xE0, 0x00, 0x11],
213
+ "GETR" => [0xE0, 0x00, 0x12],
214
+ "GETREC" => [0xE0, 0x00, 0x13],
215
+ "GETRX" => [0xE0, 0x00, 0x14],
216
+ "GETSUB" => [0xE0, 0x00, 0x15],
217
+ "GETX" => [0xE0, 0x00, 0x17],
218
+ "INSCHR" => [0xE0, 0x00, 0x1B],
219
+ "INSREC" => [0xE0, 0x00, 0x1F],
220
+ "PASN" => [0xE0, 0x00, 0x20],
221
+ "PCLPS" => [0xE0, 0x00, 0x21],
222
+ "POSA" => [0xE0, 0x00, 0x3A],
223
+ "POSFL" => [0xE0, 0x00, 0x3B],
224
+ "PSIZE" => [0xE0, 0x00, 0x38],
225
+ "PURFL" => [0xE0, 0x00, 0x4E],
226
+ "RCLFLAG" => [0xE0, 0x00, 0x61],
227
+ "RCLPT" => [0xE0, 0x00, 0x62],
228
+ "RCLPTA" => [0xE0, 0x00, 0x63],
229
+ "REGMOVE" => [0xE0, 0x00, 0x66],
230
+ "REGSWAP" => [0xE0, 0x00, 0x67],
231
+ "SAVEAS" => [0xE0, 0x00, 0x68],
232
+ "SAVEP" => [0xE0, 0x00, 0x69],
233
+ "SAVER" => [0xE0, 0x00, 0x7C],
234
+ "SAVERX" => [0xE0, 0x00, 0x7E],
235
+ "SAVEX" => [0xE0, 0x00, 0x7F],
236
+ }
237
+ if xrom_funcs[cmd_upper]
238
+ result += xrom_funcs[cmd_upper]
239
+ next
240
+ end
241
+
242
+ # Known commands
243
+ if commands[cmd_upper]
244
+ result += commands[cmd_upper]
245
+ next
246
+ end
247
+
248
+ # Unknown command - add as comment in output
249
+ puts "Warning: Cannot encode command: #{line}"
250
+ end
251
+
252
+ # Add end marker
253
+ result += [0xC0, 0x02, 0x2F]
254
+
255
+ result.pack("C*")
256
+ end
257
+
258
+ # vim:ft=ruby:
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.7'
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.7: Complete HP-41 RAW import/export with 100% decode success - convert HP-41 programs to XRPN and back with full support for 165+ opcodes including ALL math functions (SIN, COS, LOG, etc.), flow control, and numbers."
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.7'
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-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tty-prompt
@@ -28,9 +28,10 @@ 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.7: Complete HP-41
32
+ RAW import/export with 100% decode success - convert HP-41 programs to XRPN and
33
+ back with full support for 165+ opcodes including ALL math functions (SIN, COS,
34
+ LOG, etc.), flow control, and numbers.'
34
35
  email: g@isene.com
35
36
  executables:
36
37
  - xrpn
@@ -40,7 +41,9 @@ files:
40
41
  - README.md
41
42
  - bin/xrpn
42
43
  - conf
44
+ - tests/README.md
43
45
  - tests/final_test.xrpn
46
+ - tests/run_tests.rb
44
47
  - tests/test_bugs.xrpn
45
48
  - tests/test_bugs_simple.xrpn
46
49
  - tests/test_final.xrpn
@@ -226,6 +229,9 @@ files:
226
229
  - xcmd/r_p
227
230
  - xcmd/rad
228
231
  - xcmd/rand
232
+ - xcmd/rawexport
233
+ - xcmd/rawimport
234
+ - xcmd/rawinfo
229
235
  - xcmd/rcl
230
236
  - xcmd/rclaf
231
237
  - xcmd/rclflag
@@ -334,6 +340,9 @@ files:
334
340
  - xlib/locate_prg
335
341
  - xlib/numeric
336
342
  - xlib/numformat
343
+ - xlib/raw_decoder
344
+ - xlib/raw_encoder
345
+ - xlib/raw_info
337
346
  - xlib/read_state
338
347
  - xlib/read_xcmd
339
348
  - xlib/rxcmd