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.
- checksums.yaml +4 -4
- data/README.md +60 -4
- data/tests/README.md +259 -0
- data/tests/run_tests.rb +220 -0
- data/xcmd/rawexport +56 -0
- data/xcmd/rawimport +58 -0
- data/xcmd/rawinfo +22 -0
- data/xlib/_xrpn_version +1 -1
- data/xlib/raw_decoder +513 -0
- data/xlib/raw_encoder +258 -0
- data/xlib/raw_info +155 -0
- data/xrpn.gemspec +2 -2
- metadata +14 -5
data/xlib/_xrpn_version
CHANGED
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:
|