Skip to main content

uxn_tal/
opcode_table.rs

1//! UXN Opcode Reference Table
2//!
3//! This module contains the complete UXN opcode table as specified in the official
4//! UXN Tal Reference: <https://wiki.xxiivv.com/site/uxntal_reference.html>
5//!
6//! The table maps each opcode byte (0x00-0xFF) to its corresponding instruction
7//! with mode flags applied.
8
9use std::collections::HashMap;
10
11/// Complete UXN opcode reference table
12///
13/// This table represents the full 256-entry opcode map where each byte
14/// corresponds to a specific instruction with mode flags applied.
15///
16/// Mode flags are encoded in the opcode byte as follows:
17/// - Bit 7 (0x80): Keep mode (k)
18/// - Bit 6 (0x40): Return mode (r)
19/// - Bit 5 (0x20): Short mode (2)
20/// - Bits 4-0: Base instruction (0-31)
21pub const UXN_OPCODE_TABLE: &[(u8, &str)] = &[
22    // Row 0x00: Base instructions
23    (0x00, "BRK"),
24    (0x01, "INC"),
25    (0x02, "POP"),
26    (0x03, "NIP"),
27    (0x04, "SWP"),
28    (0x05, "ROT"),
29    (0x06, "DUP"),
30    (0x07, "OVR"),
31    (0x08, "EQU"),
32    (0x09, "NEQ"),
33    (0x0A, "GTH"),
34    (0x0B, "LTH"),
35    (0x0C, "JMP"),
36    (0x0D, "JCN"),
37    (0x0E, "JSR"),
38    (0x0F, "STH"),
39    // Row 0x10: Base instructions continued
40    (0x10, "LDZ"),
41    (0x11, "STZ"),
42    (0x12, "LDR"),
43    (0x13, "STR"),
44    (0x14, "LDA"),
45    (0x15, "STA"),
46    (0x16, "DEI"),
47    (0x17, "DEO"),
48    (0x18, "ADD"),
49    (0x19, "SUB"),
50    (0x1A, "MUL"),
51    (0x1B, "DIV"),
52    (0x1C, "AND"),
53    (0x1D, "ORA"),
54    (0x1E, "EOR"),
55    (0x1F, "SFT"),
56    // Row 0x20: Short mode (2) instructions
57    (0x20, "JCI"),
58    (0x21, "INC2"),
59    (0x22, "POP2"),
60    (0x23, "NIP2"),
61    (0x24, "SWP2"),
62    (0x25, "ROT2"),
63    (0x26, "DUP2"),
64    (0x27, "OVR2"),
65    (0x28, "EQU2"),
66    (0x29, "NEQ2"),
67    (0x2A, "GTH2"),
68    (0x2B, "LTH2"),
69    (0x2C, "JMP2"),
70    (0x2D, "JCN2"),
71    (0x2E, "JSR2"),
72    (0x2F, "STH2"),
73    // Row 0x30: Short mode continued
74    (0x30, "LDZ2"),
75    (0x31, "STZ2"),
76    (0x32, "LDR2"),
77    (0x33, "STR2"),
78    (0x34, "LDA2"),
79    (0x35, "STA2"),
80    (0x36, "DEI2"),
81    (0x37, "DEO2"),
82    (0x38, "ADD2"),
83    (0x39, "SUB2"),
84    (0x3A, "MUL2"),
85    (0x3B, "DIV2"),
86    (0x3C, "AND2"),
87    (0x3D, "ORA2"),
88    (0x3E, "EOR2"),
89    (0x3F, "SFT2"),
90    // Row 0x40: Return mode (r) instructions
91    (0x40, "JMI"),
92    (0x41, "INCr"),
93    (0x42, "POPr"),
94    (0x43, "NIPr"),
95    (0x44, "SWPr"),
96    (0x45, "ROTr"),
97    (0x46, "DUPr"),
98    (0x47, "OVRr"),
99    (0x48, "EQUr"),
100    (0x49, "NEQr"),
101    (0x4A, "GTHr"),
102    (0x4B, "LTHr"),
103    (0x4C, "JMPr"),
104    (0x4D, "JCNr"),
105    (0x4E, "JSRr"),
106    (0x4F, "STHr"),
107    // Row 0x50: Return mode continued
108    (0x50, "LDZr"),
109    (0x51, "STZr"),
110    (0x52, "LDRr"),
111    (0x53, "STRr"),
112    (0x54, "LDAr"),
113    (0x55, "STAr"),
114    (0x56, "DEIr"),
115    (0x57, "DEOr"),
116    (0x58, "ADDr"),
117    (0x59, "SUBr"),
118    (0x5A, "MULr"),
119    (0x5B, "DIVr"),
120    (0x5C, "ANDr"),
121    (0x5D, "ORAr"),
122    (0x5E, "EORr"),
123    (0x5F, "SFTr"),
124    // Row 0x60: Return + Short mode (2r) instructions
125    (0x60, "JSI"),
126    (0x61, "INC2r"),
127    (0x62, "POP2r"),
128    (0x63, "NIP2r"),
129    (0x64, "SWP2r"),
130    (0x65, "ROT2r"),
131    (0x66, "DUP2r"),
132    (0x67, "OVR2r"),
133    (0x68, "EQU2r"),
134    (0x69, "NEQ2r"),
135    (0x6A, "GTH2r"),
136    (0x6B, "LTH2r"),
137    (0x6C, "JMP2r"),
138    (0x6D, "JCN2r"),
139    (0x6E, "JSR2r"),
140    (0x6F, "STH2r"),
141    // Row 0x70: Return + Short mode continued
142    (0x70, "LDZ2r"),
143    (0x71, "STZ2r"),
144    (0x72, "LDR2r"),
145    (0x73, "STR2r"),
146    (0x74, "LDA2r"),
147    (0x75, "STA2r"),
148    (0x76, "DEI2r"),
149    (0x77, "DEO2r"),
150    (0x78, "ADD2r"),
151    (0x79, "SUB2r"),
152    (0x7A, "MUL2r"),
153    (0x7B, "DIV2r"),
154    (0x7C, "AND2r"),
155    (0x7D, "ORA2r"),
156    (0x7E, "EOR2r"),
157    (0x7F, "SFT2r"),
158    // Row 0x80: Keep mode (k) instructions
159    (0x80, "LIT"),
160    (0x81, "INCk"),
161    (0x82, "POPk"),
162    (0x83, "NIPk"),
163    (0x84, "SWPk"),
164    (0x85, "ROTk"),
165    (0x86, "DUPk"),
166    (0x87, "OVRk"),
167    (0x88, "EQUk"),
168    (0x89, "NEQk"),
169    (0x8A, "GTHk"),
170    (0x8B, "LTHk"),
171    (0x8C, "JMPk"),
172    (0x8D, "JCNk"),
173    (0x8E, "JSRk"),
174    (0x8F, "STHk"),
175    // Row 0x90: Keep mode continued
176    (0x90, "LDZk"),
177    (0x91, "STZk"),
178    (0x92, "LDRk"),
179    (0x93, "STRk"),
180    (0x94, "LDAk"),
181    (0x95, "STAk"),
182    (0x96, "DEIk"),
183    (0x97, "DEOk"),
184    (0x98, "ADDk"),
185    (0x99, "SUBk"),
186    (0x9A, "MULk"),
187    (0x9B, "DIVk"),
188    (0x9C, "ANDk"),
189    (0x9D, "ORAk"),
190    (0x9E, "EORk"),
191    (0x9F, "SFTk"),
192    // Row 0xA0: Keep + Short mode (2k) instructions
193    (0xA0, "LIT2"),
194    (0xA1, "INC2k"),
195    (0xA2, "POP2k"),
196    (0xA3, "NIP2k"),
197    (0xA4, "SWP2k"),
198    (0xA5, "ROT2k"),
199    (0xA6, "DUP2k"),
200    (0xA7, "OVR2k"),
201    (0xA8, "EQU2k"),
202    (0xA9, "NEQ2k"),
203    (0xAA, "GTH2k"),
204    (0xAB, "LTH2k"),
205    (0xAC, "JMP2k"),
206    (0xAD, "JCN2k"),
207    (0xAE, "JSR2k"),
208    (0xAF, "STH2k"),
209    // Row 0xB0: Keep + Short mode continued
210    (0xB0, "LDZ2k"),
211    (0xB1, "STZ2k"),
212    (0xB2, "LDR2k"),
213    (0xB3, "STR2k"),
214    (0xB4, "LDA2k"),
215    (0xB5, "STA2k"),
216    (0xB6, "DEI2k"),
217    (0xB7, "DEO2k"),
218    (0xB8, "ADD2k"),
219    (0xB9, "SUB2k"),
220    (0xBA, "MUL2k"),
221    (0xBB, "DIV2k"),
222    (0xBC, "AND2k"),
223    (0xBD, "ORA2k"),
224    (0xBE, "EOR2k"),
225    (0xBF, "SFT2k"),
226    // Row 0xC0: Keep + Return mode (kr) instructions
227    (0xC0, "LITr"),
228    (0xC1, "INCkr"),
229    (0xC2, "POPkr"),
230    (0xC3, "NIPkr"),
231    (0xC4, "SWPkr"),
232    (0xC5, "ROTkr"),
233    (0xC6, "DUPkr"),
234    (0xC7, "OVRkr"),
235    (0xC8, "EQUkr"),
236    (0xC9, "NEQkr"),
237    (0xCA, "GTHkr"),
238    (0xCB, "LTHkr"),
239    (0xCC, "JMPkr"),
240    (0xCD, "JCNkr"),
241    (0xCE, "JSRkr"),
242    (0xCF, "STHkr"),
243    // Row 0xD0: Keep + Return mode continued
244    (0xD0, "LDZkr"),
245    (0xD1, "STZkr"),
246    (0xD2, "LDRkr"),
247    (0xD3, "STRkr"),
248    (0xD4, "LDAkr"),
249    (0xD5, "STAkr"),
250    (0xD6, "DEIkr"),
251    (0xD7, "DEOkr"),
252    (0xD8, "ADDkr"),
253    (0xD9, "SUBkr"),
254    (0xDA, "MULkr"),
255    (0xDB, "DIVkr"),
256    (0xDC, "ANDkr"),
257    (0xDD, "ORAkr"),
258    (0xDE, "EORkr"),
259    (0xDF, "SFTkr"),
260    // Row 0xE0: Keep + Return + Short mode (2kr) instructions
261    (0xE0, "LIT2r"),
262    (0xE1, "INC2kr"),
263    (0xE2, "POP2kr"),
264    (0xE3, "NIP2kr"),
265    (0xE4, "SWP2kr"),
266    (0xE5, "ROT2kr"),
267    (0xE6, "DUP2kr"),
268    (0xE7, "OVR2kr"),
269    (0xE8, "EQU2kr"),
270    (0xE9, "NEQ2kr"),
271    (0xEA, "GTH2kr"),
272    (0xEB, "LTH2kr"),
273    (0xEC, "JMP2kr"),
274    (0xED, "JCN2kr"),
275    (0xEE, "JSR2kr"),
276    (0xEF, "STH2kr"),
277    // Row 0xF0: Keep + Return + Short mode continued
278    (0xF0, "LDZ2kr"),
279    (0xF1, "STZ2kr"),
280    (0xF2, "LDR2kr"),
281    (0xF3, "STR2kr"),
282    (0xF4, "LDA2kr"),
283    (0xF5, "STA2kr"),
284    (0xF6, "DEI2kr"),
285    (0xF7, "DEO2kr"),
286    (0xF8, "ADD2kr"),
287    (0xF9, "SUB2kr"),
288    (0xFA, "MUL2kr"),
289    (0xFB, "DIV2kr"),
290    (0xFC, "AND2kr"),
291    (0xFD, "ORA2kr"),
292    (0xFE, "EOR2kr"),
293    (0xFF, "SFT2kr"),
294];
295
296/// Base instruction names (without mode flags)
297pub const BASE_INSTRUCTIONS: &[&str] = &[
298    "BRK", "INC", "POP", "NIP", "SWP", "ROT", "DUP", "OVR", // 0x00-0x07
299    "EQU", "NEQ", "GTH", "LTH", "JMP", "JCN", "JSR", "STH", // 0x08-0x0F
300    "LDZ", "STZ", "LDR", "STR", "LDA", "STA", "DEI", "DEO", // 0x10-0x17
301    "ADD", "SUB", "MUL", "DIV", "AND", "ORA", "EOR", "SFT", // 0x18-0x1F
302];
303
304/// Create a hashmap from instruction name to base opcode value
305pub fn create_instruction_map() -> HashMap<String, u8> {
306    let mut map = HashMap::new();
307    for (index, &instruction) in BASE_INSTRUCTIONS.iter().enumerate() {
308        map.insert(instruction.to_string(), index as u8);
309        map.insert(instruction.to_ascii_lowercase(), index as u8);
310    }
311    map
312}
313
314/// Get the complete opcode name for a given byte value
315pub fn get_opcode_name(opcode: u8) -> &'static str {
316    UXN_OPCODE_TABLE[opcode as usize].1
317}
318
319/// Decode an opcode byte into its components
320pub fn decode_opcode(opcode: u8) -> (u8, bool, bool, bool) {
321    let base = opcode & 0x1F; // Bits 0-4: base instruction
322    let short_mode = (opcode & 0x20) != 0; // Bit 5: short mode
323    let return_mode = (opcode & 0x40) != 0; // Bit 6: return mode
324    let keep_mode = (opcode & 0x80) != 0; // Bit 7: keep mode
325
326    (base, short_mode, return_mode, keep_mode)
327}
328
329/// Encode an opcode from base instruction and mode flags
330pub fn encode_opcode(base: u8, short_mode: bool, return_mode: bool, keep_mode: bool) -> u8 {
331    let mut opcode = base & 0x1F;
332    if short_mode {
333        opcode |= 0x20;
334    }
335    if return_mode {
336        opcode |= 0x40;
337    }
338    if keep_mode {
339        opcode |= 0x80;
340    }
341    opcode
342}
343
344/// Verify that our opcode table matches the official specification
345pub fn verify_opcode_table() -> Result<(), String> {
346    // Check that we have exactly 256 entries
347    if UXN_OPCODE_TABLE.len() != 256 {
348        return Err(format!(
349            "Expected 256 opcodes, found {}",
350            UXN_OPCODE_TABLE.len()
351        ));
352    }
353
354    // Check that opcodes are in order
355    for (i, &(opcode, _)) in UXN_OPCODE_TABLE.iter().enumerate() {
356        if opcode != i as u8 {
357            return Err(format!(
358                "Opcode at index {} should be 0x{:02x}, found 0x{:02x}",
359                i, i, opcode
360            ));
361        }
362    }
363
364    // Check special opcodes
365    let special_checks = [
366        (0x00, "BRK"),
367        (0x80, "LIT"),
368        (0xA0, "LIT2"),
369        (0x17, "DEO"),
370        (0x20, "JCI"),
371        (0x40, "JMI"),
372        (0x60, "JSI"),
373    ];
374
375    for &(opcode, expected_name) in &special_checks {
376        let actual_name = get_opcode_name(opcode);
377        if actual_name != expected_name {
378            return Err(format!(
379                "Opcode 0x{:02x} should be '{}', found '{}'",
380                opcode, expected_name, actual_name
381            ));
382        }
383    }
384
385    println!("✓ UXN opcode table verification passed");
386    Ok(())
387}
388
389#[cfg(test)]
390mod tests {
391    use super::*;
392
393    #[test]
394    fn test_opcode_table_verification() {
395        verify_opcode_table().unwrap();
396    }
397
398    #[test]
399    fn test_opcode_encoding() {
400        // Test LIT (0x80)
401        assert_eq!(encode_opcode(0x00, false, false, true), 0x80);
402
403        // Test LIT2 (0xA0)
404        assert_eq!(encode_opcode(0x00, true, false, true), 0xA0);
405
406        // Test DEO (0x17)
407        assert_eq!(encode_opcode(0x17, false, false, false), 0x17);
408
409        // Test ADD2k (0xB8)
410        assert_eq!(encode_opcode(0x18, true, false, true), 0xB8);
411    }
412
413    #[test]
414    fn test_opcode_decoding() {
415        // Test LIT (0x80)
416        let (base, short, ret, keep) = decode_opcode(0x80);
417        assert_eq!(base, 0x00);
418        assert!(!short);
419        assert!(!ret);
420        assert!(keep);
421
422        // Test ADD2kr (0xF8)
423        let (base, short, ret, keep) = decode_opcode(0xF8);
424        assert_eq!(base, 0x18);
425        assert!(short);
426        assert!(ret);
427        assert!(keep);
428    }
429
430    #[test]
431    fn test_instruction_names() {
432        assert_eq!(get_opcode_name(0x00), "BRK");
433        assert_eq!(get_opcode_name(0x80), "LIT");
434        assert_eq!(get_opcode_name(0x17), "DEO");
435        assert_eq!(get_opcode_name(0xB8), "ADD2k");
436        assert_eq!(get_opcode_name(0xFF), "SFT2kr");
437    }
438}