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/_xrpn_version CHANGED
@@ -1,5 +1,5 @@
1
1
  def xrpn_version
2
- puts "XRPN version: 2.5"
2
+ puts "XRPN version: 2.7"
3
3
  puts "RubyGem version: " + Gem.latest_spec_for("xrpn").version.to_s
4
4
  end
5
5
 
data/xlib/raw_decoder ADDED
@@ -0,0 +1,513 @@
1
+ # HP-41 RAW File Decoder
2
+ # Converts RAW bytecode to XRPN text format
3
+
4
+ def raw_decode(data)
5
+ # Opcode mapping table (from raw.md documentation + Phase 1)
6
+ opcodes = {
7
+ # Arithmetic
8
+ 0x81 => "+",
9
+ 0x82 => "-",
10
+ 0x83 => "*",
11
+ 0x84 => "/",
12
+ 0x85 => "END",
13
+ 0x86 => "SQRT",
14
+ 0x87 => "CLST", # Clear stack
15
+
16
+ # Math functions (Phase 4) - Single-byte but context-sensitive
17
+ # These overlap with ASCII (0x50-0x66) so only valid after non-ASCII bytes
18
+ 0x50 => "LN", # Natural log
19
+ 0x51 => "X^2", # X squared
20
+ 0x52 => "SQRT", # Square root
21
+ 0x53 => "POW", # Y^X
22
+ 0x54 => "CHS", # Change sign (already in 0x8B but also here)
23
+ 0x55 => "E^X", # e to the X
24
+ 0x56 => "LOG", # Base-10 log
25
+ 0x57 => "10^X", # 10 to the X
26
+ 0x58 => "E^X-1", # e^X - 1
27
+ 0x59 => "SIN", # Sine
28
+ 0x5A => "COS", # Cosine
29
+ 0x5B => "TAN", # Tangent
30
+ 0x5C => "ASIN", # Arc sine
31
+ 0x5D => "ACOS", # Arc cosine
32
+ 0x5E => "ATAN", # Arc tangent
33
+ 0x60 => "1/X", # Reciprocal
34
+ 0x61 => "ABS", # Absolute value
35
+ 0x65 => "LN1+X", # ln(1+X)
36
+
37
+ # Stack operations
38
+ 0x88 => "ENTER",
39
+ 0x89 => "SWAP", # X<>Y
40
+ 0x8A => "RDN", # Roll down
41
+ 0x8B => "CHS", # Change sign
42
+ 0x8C => "LASTX",
43
+ 0x8D => "CLX",
44
+
45
+ # Display/IO operations
46
+ 0x80 => "CLA", # Clear alpha
47
+ 0x8E => "PROMPT",
48
+ 0x9B => "AVIEW",
49
+ 0x9C => "PSE", # Pause
50
+ 0x9D => "BEEP",
51
+ 0x9E => "STOP",
52
+
53
+ # Extended operations (Phase 2)
54
+ 0x92 => "X<>", # X exchange (with register parameter)
55
+ 0x93 => "DEG", # Degree mode
56
+ 0x94 => "RAD", # Radian mode
57
+ 0x95 => "GRAD", # Gradian mode
58
+ 0x96 => "FIX", # Fix decimal places (parameter follows)
59
+ 0x97 => "SCI", # Scientific notation
60
+ 0x98 => "ENG", # Engineering notation
61
+ 0x99 => "RCL", # RCL variant
62
+ 0x9A => "DSE", # Decrement and skip if equal (with register)
63
+ 0x9F => "ISG", # Increment and skip if greater
64
+
65
+ # Flow control
66
+ 0x8F => "RTN", # Return
67
+
68
+ # Additional useful opcodes
69
+ 0xCE => "ASTO", # Append string to alpha (with register)
70
+ 0x7E => "~", # Tilde separator
71
+ 0x7F => "\"", # Quote character
72
+
73
+ # Arithmetic variants (0x40-0x43)
74
+ 0x40 => "+", # Addition variant
75
+ 0x41 => "-", # Subtraction variant
76
+ 0x42 => "*", # Multiplication variant
77
+ 0x43 => "/", # Division variant
78
+
79
+ # Control flow markers (not commands)
80
+ 0x00 => nil, # NOP/padding - handled specially
81
+ }
82
+
83
+ result = []
84
+ pos = 0
85
+ in_number_context = false # Track if we just saw an operation/END
86
+
87
+ while pos < data.length
88
+ byte = data[pos].ord
89
+
90
+ # Global label header (C0 00 Fx 00 LABELNAME)
91
+ if byte == 0xC0 && pos + 3 < data.length && data[pos + 1].ord == 0x00
92
+ label_type = data[pos + 2].ord
93
+ pos += 4
94
+
95
+ # Extract label name (ASCII letters, digits, space 0x20-0x7E)
96
+ label = ""
97
+ while pos < data.length
98
+ b = data[pos].ord
99
+ # Accept alphanumeric and common symbols (0x20-0x7E)
100
+ # But NOT control chars (< 0x20) or high-bit (>= 0x80)
101
+ # Special case: 0x7F (DEL) terminates
102
+ break if b < 0x20 || b >= 0x7F
103
+ label << data[pos]
104
+ pos += 1
105
+ end
106
+
107
+ result << "LBL \"#{label}\""
108
+ in_number_context = true # Numbers can follow labels
109
+ next
110
+ end
111
+
112
+ # Variant header (C6 00 Fx 00 LABELNAME)
113
+ if byte == 0xC6 && pos + 3 < data.length && data[pos + 1].ord == 0x00
114
+ pos += 4
115
+
116
+ # Extract label name
117
+ label = ""
118
+ while pos < data.length
119
+ b = data[pos].ord
120
+ break if b < 0x20 || b >= 0x7F
121
+ label << data[pos]
122
+ pos += 1
123
+ end
124
+
125
+ result << "LBL \"#{label}\""
126
+ in_number_context = true
127
+ next
128
+ end
129
+
130
+ # Local label (F6 00 LABELNAME or F6 LABELNAME)
131
+ if byte == 0xF6
132
+ if pos + 1 < data.length && data[pos + 1].ord == 0x00
133
+ pos += 2
134
+ else
135
+ pos += 1
136
+ end
137
+
138
+ # Extract label name
139
+ label = ""
140
+ while pos < data.length
141
+ b = data[pos].ord
142
+ break if b < 0x20 || b >= 0x7F
143
+ label << data[pos]
144
+ pos += 1
145
+ end
146
+
147
+ result << "LBL \"#{label}\""
148
+ in_number_context = true
149
+ next
150
+ end
151
+
152
+ # F2 XX YY pattern - Parameter/data encoding
153
+ if byte == 0xF2 && pos + 2 < data.length
154
+ param = data[pos + 1].ord
155
+ marker = data[pos + 2].ord
156
+ # F2 is a data marker - skip the 3-byte sequence
157
+ pos += 3
158
+ next
159
+ end
160
+
161
+ # String literals (various formats F4, F5, F9, FA, FB, FD)
162
+ if [0xF4, 0xF5, 0xF9, 0xFA, 0xFB, 0xFD].include?(byte) && pos + 1 < data.length
163
+ string_type = byte
164
+ pos += 1
165
+ text = ""
166
+ while pos < data.length && data[pos].ord >= 0x20 && data[pos].ord < 0x7F
167
+ text << data[pos]
168
+ pos += 1
169
+ end
170
+ # Output string unless empty
171
+ unless text.empty?
172
+ result << "\"#{text}\""
173
+ end
174
+ in_number_context = false # Strings break number context
175
+ next
176
+ end
177
+
178
+ # Flag operations (A8-AE range) - CHECK BEFORE RCL!
179
+ if byte >= 0xA8 && byte <= 0xAE && pos + 1 < data.length
180
+ flag_num = data[pos + 1].ord
181
+ flag_ops = {
182
+ 0xA8 => "SF", # Set flag
183
+ 0xA9 => "CF", # Clear flag
184
+ 0xAA => "FS?", # Flag set?
185
+ 0xAB => "FC?", # Flag clear?
186
+ 0xAC => "FS?C", # Flag set? then clear
187
+ 0xAD => "FC?C", # Flag clear? then clear
188
+ 0xAE => "FSC?", # Flag set/clear?
189
+ }
190
+ if flag_ops[byte]
191
+ result << "#{flag_ops[byte]} #{flag_num}"
192
+ pos += 2
193
+ next
194
+ end
195
+ end
196
+
197
+ # RCL operations (A7 + register number)
198
+ if byte == 0xA7 && pos + 1 < data.length
199
+ reg_num = data[pos + 1].ord
200
+ result << "RCL #{reg_num}"
201
+ pos += 2
202
+ next
203
+ end
204
+
205
+ # STO operations (B2-BB range + register number)
206
+ if byte >= 0xB2 && byte <= 0xBB && pos + 1 < data.length
207
+ reg_num = data[pos + 1].ord
208
+ result << "STO #{reg_num}"
209
+ pos += 2
210
+ next
211
+ end
212
+
213
+ # VIEW operations (9A-9C variants)
214
+ if byte >= 0x9A && byte <= 0x9C && pos + 1 < data.length
215
+ next_byte = data[pos + 1].ord
216
+ if next_byte == 0x73
217
+ result << "AVIEW"
218
+ pos += 2
219
+ elsif next_byte == 0x72
220
+ result << "VIEW"
221
+ pos += 2
222
+ else
223
+ result << "VIEW #{next_byte}"
224
+ pos += 2
225
+ end
226
+ next
227
+ end
228
+
229
+ # GTO (0x90 + label reference or register)
230
+ if byte == 0x90 && pos + 1 < data.length
231
+ next_byte = data[pos + 1].ord
232
+ if next_byte >= 0x41 && next_byte <= 0x5A # ASCII A-Z
233
+ # Label name follows
234
+ label = ""
235
+ pos += 1
236
+ while pos < data.length && data[pos].ord >= 0x20 && data[pos].ord < 0x7F
237
+ label << data[pos]
238
+ pos += 1
239
+ end
240
+ result << "GTO \"#{label}\""
241
+ else
242
+ result << "GTO #{next_byte}"
243
+ pos += 2
244
+ end
245
+ next
246
+ end
247
+
248
+ # XEQ (0x91 + label reference)
249
+ if byte == 0x91 && pos + 1 < data.length
250
+ next_byte = data[pos + 1].ord
251
+ if next_byte >= 0x41 && next_byte <= 0x5A # ASCII A-Z
252
+ # Label name follows
253
+ label = ""
254
+ pos += 1
255
+ while pos < data.length && data[pos].ord >= 0x20 && data[pos].ord < 0x7F
256
+ label << data[pos]
257
+ pos += 1
258
+ end
259
+ result << "XEQ \"#{label}\""
260
+ else
261
+ result << "XEQ #{next_byte}"
262
+ pos += 2
263
+ end
264
+ next
265
+ end
266
+
267
+ # Direct register operations (Phase 5) - Single byte RCL/STO
268
+ # 0x20-0x2F = RCL 00-15, 0x30-0x3F = STO 00-15
269
+ if byte >= 0x20 && byte <= 0x2F && pos > 0
270
+ prev_byte = data[pos - 1].ord
271
+ # Safe if after non-ASCII (avoid being part of strings)
272
+ if prev_byte >= 0x80 || prev_byte <= 0x1F
273
+ reg = byte - 0x20
274
+ result << "RCL #{sprintf('%02d', reg)}"
275
+ pos += 1
276
+ in_number_context = true
277
+ next
278
+ end
279
+ end
280
+
281
+ if byte >= 0x30 && byte <= 0x3F && pos > 0
282
+ prev_byte = data[pos - 1].ord
283
+ # Safe if after non-ASCII
284
+ if prev_byte >= 0x80 || prev_byte <= 0x1F
285
+ reg = byte - 0x30
286
+ result << "STO #{sprintf('%02d', reg)}"
287
+ pos += 1
288
+ in_number_context = true
289
+ next
290
+ end
291
+ end
292
+
293
+ # Math functions in ASCII range (0x50-0x66) - Phase 4
294
+ # Only decode as math if NOT in label/string context
295
+ # Safe after: opcodes >= 0x80, numbers 0x00-0x1F, END (0x85)
296
+ if byte >= 0x50 && byte <= 0x66 && pos > 0
297
+ prev_byte = data[pos - 1].ord
298
+ # Safe context: after operations, numbers, or non-ASCII
299
+ if prev_byte >= 0x80 || prev_byte <= 0x1F || prev_byte == 0x85
300
+ math_ops = {
301
+ 0x50 => "LN",
302
+ 0x51 => "X^2",
303
+ 0x52 => "SQRT",
304
+ 0x53 => "POW",
305
+ 0x54 => "CHS",
306
+ 0x55 => "E^X",
307
+ 0x56 => "LOG",
308
+ 0x57 => "10^X",
309
+ 0x58 => "E^X-1",
310
+ 0x59 => "SIN",
311
+ 0x5A => "COS",
312
+ 0x5B => "TAN",
313
+ 0x5C => "ASIN",
314
+ 0x5D => "ACOS",
315
+ 0x5E => "ATAN",
316
+ 0x60 => "1/X",
317
+ 0x61 => "ABS",
318
+ 0x65 => "LN1+X",
319
+ }
320
+ if math_ops[byte]
321
+ result << math_ops[byte]
322
+ pos += 1
323
+ in_number_context = true
324
+ next
325
+ end
326
+ end
327
+ end
328
+
329
+ # Numeric literals (Phase 5) - 0x00-0x63 = 0-99
330
+ # Safe after: operations (0x80+), END (0x85), labels, or in number context
331
+ if byte >= 0x00 && byte <= 0x63 && pos > 0
332
+ prev_byte = data[pos - 1].ord
333
+ # Multiple safe contexts
334
+ safe_contexts = [
335
+ prev_byte >= 0x80, # After operations
336
+ prev_byte == 0x85, # After END
337
+ in_number_context, # After recent operation
338
+ prev_byte >= 0x00 && prev_byte <= 0x1F # After another number
339
+ ]
340
+
341
+ if safe_contexts.any?
342
+ result << byte.to_s
343
+ pos += 1
344
+ in_number_context = true
345
+ next
346
+ end
347
+ end
348
+
349
+ # XROM multi-byte instructions (Phase 3)
350
+ # Format: PREFIX MODULE FUNCTION
351
+ if (byte == 0xE0 || byte == 0xD0 || byte == 0xCF) && pos + 2 < data.length
352
+ prefix = byte
353
+ mod_id = data[pos + 1].ord
354
+ func_id = data[pos + 2].ord
355
+
356
+ # E0 = XROM extended functions
357
+ if prefix == 0xE0 && mod_id == 0x00
358
+ xrom_funcs = {
359
+ 0x00 => "ANUM",
360
+ 0x01 => "ALENG",
361
+ 0x02 => "APPCHR",
362
+ 0x03 => "APPREC",
363
+ 0x04 => "ARCLREC",
364
+ 0x05 => "AROT",
365
+ 0x06 => "ATOX",
366
+ 0x07 => "CLFL",
367
+ 0x08 => "CLKEYS",
368
+ 0x09 => "CRFLAS",
369
+ 0x0A => "CRFLD",
370
+ 0x0B => "DELCHR",
371
+ 0x0C => "DELREC",
372
+ 0x0D => "EMDIR",
373
+ 0x0E => "FLSIZE",
374
+ 0x0F => "GETAS",
375
+ 0x10 => "GETKEY",
376
+ 0x11 => "GETP",
377
+ 0x12 => "GETR",
378
+ 0x13 => "GETREC",
379
+ 0x14 => "GETRX",
380
+ 0x15 => "GETSUB",
381
+ 0x17 => "GETX",
382
+ 0x1B => "INSCHR",
383
+ 0x1F => "INSREC",
384
+ 0x20 => "PASN",
385
+ 0x21 => "PCLPS",
386
+ 0x3A => "POSA",
387
+ 0x3B => "POSFL",
388
+ 0x38 => "PSIZE",
389
+ 0x4E => "PURFL",
390
+ 0x61 => "RCLFLAG",
391
+ 0x62 => "RCLPT",
392
+ 0x63 => "RCLPTA",
393
+ 0x66 => "REGMOVE",
394
+ 0x67 => "REGSWAP",
395
+ 0x68 => "SAVEAS",
396
+ 0x69 => "SAVEP",
397
+ 0x7C => "SAVER",
398
+ 0x7E => "SAVERX",
399
+ 0x7F => "SAVEX",
400
+ }
401
+ if xrom_funcs[func_id]
402
+ result << xrom_funcs[func_id]
403
+ pos += 3
404
+ next
405
+ else
406
+ result << "XROM 00,#{func_id}"
407
+ pos += 3
408
+ next
409
+ end
410
+ end
411
+
412
+ # D0 00 XX = Data/register operations (synthetic/advanced)
413
+ if prefix == 0xD0 && mod_id == 0x00
414
+ # These are advanced data operations - treat as NOP for now
415
+ # Don't add to result, just skip the bytes
416
+ pos += 3
417
+ next
418
+ end
419
+
420
+ # CF XX YY = Control flow extended (synthetic/advanced)
421
+ if prefix == 0xCF
422
+ # Advanced control flow - treat as NOP for now
423
+ # Don't add to result, just skip the bytes
424
+ pos += 3
425
+ next
426
+ end
427
+
428
+ # Unknown XROM - output as comment
429
+ result << "# XROM #{mod_id},#{func_id}"
430
+ pos += 3
431
+ next
432
+ end
433
+
434
+ # Conditionals (0x67-0x78 range for X comparisons)
435
+ if byte >= 0x67 && byte <= 0x78
436
+ cond_map = {
437
+ 0x67 => "X=0?",
438
+ 0x68 => "X!=0?",
439
+ 0x69 => "X>0?",
440
+ 0x6A => "X<0?",
441
+ 0x6B => "X>=0?",
442
+ 0x6C => "X<=0?",
443
+ 0x71 => "X=Y?",
444
+ 0x72 => "X!=Y?",
445
+ 0x73 => "X>Y?",
446
+ 0x74 => "X<Y?",
447
+ 0x75 => "X>=Y?",
448
+ 0x76 => "X<=Y?",
449
+ 0x78 => "X<>Y?", # X exchange Y test
450
+ }
451
+ if cond_map[byte]
452
+ result << cond_map[byte]
453
+ else
454
+ result << "# Conditional: 0x#{byte.to_s(16).upcase}"
455
+ end
456
+ pos += 1
457
+ next
458
+ end
459
+
460
+ # Numeric literals (Phase 4 - EXPERIMENTAL)
461
+ # Numbers 0x00-0x63 (0-99) but only in safe contexts
462
+ # This is tricky because 0x10-0x7F overlaps with ASCII and opcodes
463
+ # For now, skip numeric literal auto-detection to avoid false positives
464
+ # TODO: Need more research on exact number encoding rules
465
+
466
+ # Single digit literals via F1 XX (alternate encoding)
467
+ if byte == 0xF1 && pos + 1 < data.length
468
+ digit = data[pos + 1].ord
469
+ if digit >= 0x30 && digit <= 0x39
470
+ result << (digit - 0x30).to_s # 0-9
471
+ elsif digit >= 0x41 && digit <= 0x46
472
+ result << ((digit - 0x41 + 10)).to_s # A-F (10-15)
473
+ end
474
+ pos += 2
475
+ next
476
+ end
477
+
478
+ # Known single-byte opcodes
479
+ if opcodes[byte]
480
+ result << opcodes[byte]
481
+ pos += 1
482
+ # Set number context for operations that commonly precede numbers
483
+ in_number_context = [0x85, 0x8E].include?(byte) # After END, PROMPT
484
+ next
485
+ end
486
+
487
+ # End markers - stop processing
488
+ if byte == 0xC2 || byte == 0xC8 || byte == 0xCA
489
+ break
490
+ end
491
+
492
+ # End marker (C0 02 2F)
493
+ if byte == 0xC0 && pos + 2 < data.length
494
+ if data[pos + 1].ord == 0x02 && data[pos + 2].ord == 0x2F
495
+ break
496
+ end
497
+ end
498
+
499
+ # Unknown opcode - output as comment
500
+ if byte > 0x20 && byte < 0x7F
501
+ # Skip - might be part of string
502
+ pos += 1
503
+ else
504
+ result << "# Unknown: 0x#{byte.to_s(16).upcase}"
505
+ pos += 1
506
+ end
507
+ end
508
+
509
+ # Remove nil entries and return
510
+ result.compact
511
+ end
512
+
513
+ # vim:ft=ruby: