base/instruction/
format.rs

1/// Human-oriented formatting for instructions (or parts of instructions).
2use std::fmt::{self, Display, Formatter, Octal, Write};
3
4use super::super::bitselect::{BitPos, BitSelector, bit_select};
5use super::super::charset::{
6    NoSubscriptKnown, NoSuperscriptKnown, subscript_char, superscript_char,
7};
8use super::super::instruction::{
9    DisassemblyFailure, Inst, Opcode, OperandAddress, SkmBitSelector, SymbolicInstruction,
10    index_address_to_bit_selection,
11};
12use super::super::prelude::*;
13
14/// Convert an opcode to its text representation.
15///
16/// The primary (i.e. not supernumerary) opcode mnemonic is used,
17/// because the configuration value which would identify a
18/// supernumerary opcode is not passed to the `fmt` method of the
19/// `Display` trait.
20impl Display for Opcode {
21    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
22        use Opcode::*;
23        f.write_str(match self {
24            Opr => "OPR",
25            Jmp => "JMP",
26            Jpx => "JPX",
27            Jnx => "JNX",
28            Aux => "AUX",
29            Rsx => "RSX",
30            Skx => "SKX",
31            Exx => "EXX",
32            Adx => "ADX",
33            Dpx => "DPX",
34            Skm => "SKM",
35            Lde => "LDE",
36            Spf => "SPF",
37            Spg => "SPG",
38            Lda => "LDA",
39            Ldb => "LDB",
40            Ldc => "LDC",
41            Ldd => "LDD",
42            Ste => "STE",
43            Flf => "FLF",
44            Flg => "FLG",
45            Sta => "STA",
46            Stb => "STB",
47            Stc => "STC",
48            Std => "STD",
49            Ite => "ITE",
50            Ita => "ITA",
51            Una => "UNA",
52            Sed => "SED",
53            Jov => "JOV",
54            Jpa => "JPA",
55            Jna => "JNA",
56            Exa => "EXA",
57            Ins => "INS",
58            Com => "COM",
59            Tsd => "TSD",
60            Cya => "CYA",
61            Cyb => "CYB",
62            Cab => "CAB",
63            Noa => "NOA",
64            Dsa => "DSA",
65            Nab => "NAB",
66            Add => "ADD",
67            Sca => "SCA",
68            Scb => "SCB",
69            Sab => "SAB",
70            Tly => "TLY",
71            Div => "DIV",
72            Mul => "MUL",
73            Sub => "SUB",
74        })
75    }
76}
77
78/// Format an operand address in octal.
79///
80/// Deferred addresses are formatted in square brackets.  TX-2
81/// documentation seems variously to represent deferred addressing
82/// with `[...]` or `*`.  We use `[...]`.
83impl Octal for OperandAddress {
84    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
85        let (defer, physical_address) = self.split();
86        if defer {
87            f.write_char('[')?;
88        }
89        write!(f, "{physical_address:o}")?;
90        if defer {
91            f.write_char(']')?;
92        }
93        Ok(())
94    }
95}
96
97impl Display for DisassemblyFailure {
98    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
99        let opcode = match self {
100            DisassemblyFailure::InvalidOpcode(n) => {
101                f.write_str("invalid opcode")?;
102                n
103            }
104            DisassemblyFailure::UnimplementedOpcode(n) => {
105                f.write_str("unimplemented opcode")?;
106                n
107            }
108        };
109        write!(f, " {opcode:03o}")
110    }
111}
112
113fn octal_superscript_u8(n: u8) -> Result<String, NoSuperscriptKnown> {
114    format!("{n:o}").chars().map(superscript_char).collect()
115}
116
117fn subscript(s: &str) -> Result<String, NoSubscriptKnown> {
118    s.chars().map(subscript_char).collect()
119}
120
121fn octal_subscript_number(n: u8) -> String {
122    subscript(&format!("{n:o}")).unwrap()
123}
124
125fn write_opcode(
126    op: Opcode,
127    cfg: Unsigned5Bit,
128    base_address_bits: Unsigned18Bit,
129    f: &mut Formatter<'_>,
130) -> Result<(), fmt::Error> {
131    let w = Unsigned36Bit::from(base_address_bits);
132    let bit_2_8 = bit_select(
133        w,
134        BitSelector {
135            quarter: Quarter::Q2,
136            bitpos: BitPos::B8,
137        },
138    );
139    let bit_2_7 = bit_select(
140        w,
141        BitSelector {
142            quarter: Quarter::Q2,
143            bitpos: BitPos::B8,
144        },
145    );
146    // Where there is a supernumerary mnemonic, prefer it.
147    // This list is taken from table 7-3 in the Users
148    // Handbook.
149    let cfg = u8::from(cfg);
150    match op {
151        Opcode::Opr => f.write_str(if bit_2_8 {
152            "OPR"
153        } else if bit_2_7 {
154            "AOP"
155        } else {
156            "IOS"
157        }),
158        Opcode::Jmp => f.write_str(
159            #[allow(clippy::match_same_arms)] // numerical order
160            match cfg {
161                0o00 => "JMP",
162                0o01 => "BRC",
163                0o02 => "JPS",
164                0o03 => "BRS",
165                0o14 => "JPQ",
166                0o15 => "BPQ",
167                0o16 => "JES",
168                0o20 => "JPD",
169                0o21 => "JMP",
170                0o22 => "JDS",
171                0o23 => "BDS",
172                _ => "JMP",
173            },
174        ),
175        Opcode::Skx => f.write_str(match cfg {
176            0o00 => "REX",
177            0o02 => "INX",
178            0o03 => "DEX",
179            0o04 => "SXD",
180            0o06 => "SXL",
181            0o07 => "SXG",
182            0o10 => "RXF",
183            0o20 => "RXD",
184            0o30 => "RFD",
185            _ => "SKX",
186        }),
187        Opcode::Skm => f.write_str(
188            #[allow(clippy::match_same_arms)] // numerical order
189            match cfg {
190                0o00 => "SKM",
191                0o01 => "MKC",
192                0o02 => "MKZ",
193                0o03 => "MKN",
194                0o10 => "SKU",
195                0o11 => "SUC",
196                0o12 => "SUZ",
197                0o13 => "SUN",
198                0o20 => "SKZ",
199                0o21 => "SZC",
200                0o22 => "SZZ",
201                0o23 => "SZN",
202                0o30 => "SKN",
203                0o31 => "SNC",
204                0o32 => "SNZ",
205                0o33 => "SNN",
206                0o04 => "CYR",
207                0o05 => "MCR",
208                0o06 => "MZR",
209                0o07 => "MNR",
210                0o34 => "SNR",
211                0o24 => "SZR",
212                0o14 => "SUR",
213                _ => "SKM",
214            },
215        ),
216        _ => write!(f, "{op}"),
217    }
218}
219
220impl Display for SymbolicInstruction {
221    /// Convert a `SymbolicInstruction` to text (Unicode) form.
222    ///
223    /// We use supernumerary opcode mnemonics where one is suitable
224    /// (though we keep the original configuration value).
225    /// Configuration values are rendered as superscripts.  Index
226    /// addresses are rendered as subscripts and operand addresses as
227    /// normal digits.  These match the conventions used in the TX-2
228    /// User Handbook.
229    ///
230    /// The User Handbook indicates that the hold bit should be
231    /// represented as _h_ (lower-case "H") when 1 and as _h_ with
232    /// overbar when 0.  We use &#x0127; (a Unicode lower-case h with
233    /// stroke) to signal that.  When the defer bit takes the value
234    /// which is the default for the current instruction, nothing
235    /// (neither "h" nor "&#x0127;") is printed.
236    ///
237    /// This documentation comment previously said that Sketchpad used
238    /// ':' for hold, but I cannot find an example of this in Jurij's
239    /// transcription, so the statement that Sketchpad used ':' must
240    /// have been wrong.
241    ///
242    /// The representation of instructions may change over time as we
243    /// discover archival material containing program listings.  The
244    /// idea is to generally be consistent with the materials we have
245    /// available.
246    ///
247    /// Instructions such as SKM should show the index address as a
248    /// bit selector, but this may not yet happen in all the cases we
249    /// would want it.  This will change over time as we implement
250    /// more of the instruction opcodes in the emulator.
251    ///
252    /// Some addresses (arithmetic unit registers for example) are
253    /// "well-known" but we do not currently display these in symbolic
254    /// form.
255    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
256        // This implementation of Display is incomplete because, for
257        // example there are some instructions for which the index
258        // value is rendered as X.Y (I believe these are the
259        // bit-manipulation instructions).  The I/O instructions also
260        // have special cases.
261        //
262        // We also don't render "special" addresses, such as the
263        // addresses of actual registers, in symbolic form.
264        match (self.opcode().hold_is_implicit(), self.is_held()) {
265            (true, false) => {
266                // I didn't find any examples of this (a programmer
267                // override to make a normally-held opcode actually
268                // not held) in the documentation so far.
269                //
270                // We will use the notation given in section 6-2.1
271                // ("Instruction Words") of the User Handbook.
272                f.write_str("\u{0127} ")?; // lower-case h with stroke
273            }
274            (false, true) => {
275                // Some documentation uses ':' to indicate a held
276                // instruction, but this is not part of the Lincoln
277                // Writer character set.
278                f.write_str("h ")?;
279            }
280            _ => {
281                // This is the default, so it needs no annotation.
282            }
283        }
284        if !self.configuration().is_zero() {
285            let cf: u8 = self.configuration().into();
286            f.write_str(&octal_superscript_u8(cf).unwrap())?;
287        }
288        let base_address = self.operand_address_and_defer_bit();
289        write_opcode(self.opcode(), self.configuration(), base_address, f)?;
290        let j = self.index_address();
291        match self.opcode() {
292            Opcode::Skm => {
293                // The index address field in SKM instructions
294                // identify a bit in the operand to operate on, and
295                // are shown in the form "q.b".  The "q" identifies
296                // the quarter and the "b" the bit.
297                let selector: SkmBitSelector = index_address_to_bit_selection(j);
298                let rendering: String = subscript(&selector.to_string()).unwrap();
299                f.write_str(&rendering)?;
300            }
301            _ => {
302                if j != 0 {
303                    f.write_str(&octal_subscript_number(u8::from(j)))?;
304                }
305            }
306        }
307        write!(f, " {:>08o}", self.operand_address()) // includes [] if needed.
308    }
309}
310
311#[cfg(test)]
312mod tests {
313    use super::*;
314
315    fn config_value(n: u8) -> Unsigned5Bit {
316        Unsigned5Bit::try_from(n).expect("valid test data")
317    }
318
319    #[test]
320    fn test_octal_superscript_u8() {
321        assert_eq!(
322            octal_superscript_u8(0),
323            Ok("\u{2070}".to_string()),
324            "0 decimal is 0 octal"
325        );
326        assert_eq!(
327            octal_superscript_u8(1),
328            Ok("\u{00B9}".to_string()),
329            "1 decimal is 1 octal"
330        );
331        assert_eq!(
332            octal_superscript_u8(4),
333            Ok("\u{02074}".to_string()),
334            "4 decimal is 4 octal"
335        );
336        assert_eq!(
337            octal_superscript_u8(11),
338            Ok("\u{00B9}\u{00B3}".to_string()),
339            "11 decimal is 13 octal"
340        );
341        assert_eq!(
342            octal_superscript_u8(255),
343            Ok("\u{00B3}\u{2077}\u{2077}".to_string()),
344            "255 decimal is 377 octal"
345        );
346    }
347
348    #[test]
349    fn test_display_jmp() {
350        let sinst = SymbolicInstruction {
351            operand_address: OperandAddress::direct(Address::from(u18!(0o0_377_750))),
352            index: Unsigned6Bit::ZERO,
353            opcode: Opcode::Jmp,
354            configuration: Unsigned5Bit::ZERO,
355            held: false,
356        };
357        assert_eq!(&sinst.to_string(), "JMP 377750");
358    }
359
360    #[test]
361    fn test_display_jpx_sutherland() {
362        // Example from the plotter service routine from Ivan Sutherland's
363        // SKETCHPAD, as held by the Computer History museum, PDF file
364        // 102726903-05-02-acc.pdf page 124 (hand-written page number
365        // 274).
366        //
367        // -1 JPX $ UNITS OFF1        76 06 34 200 156
368        //
369        // The $ there is a placeholder (just in this comment) for a blob
370        // on the scanned page that I simply can't read.  The assembled
371        // word on the right-hand side is easier to read, but the value
372        // there (34 octal) doesn't seem to correspond very well with what
373        // looks like a single digit index value.
374        //
375        // Based on Jurij's transcription of the Sketchpad code, I
376        // think $ is actually @alpha@.  From the sk2 transcript:
377        //
378        // 4986 S1@alpha@= 34
379        // [..]
380        // 5002 @alpha@= S1@alpha@
381        // [..]
382        // 5337 @sup_minus@@sup_1@JPX @sub_alpha@ UNITS OFF1
383        //
384        // Hold is used in conjunction with the JPX opcode in order to
385        // prevent DISMISS (see page 3-26 of the User Guide which
386        // describes JPX).  Hence the -1 seems reasonable, though in
387        // any case M4 automatically puts a hold on JPX (as described
388        // on page 3-27 of the user guide).
389        //
390        // The TSX-2 has a front-panel button "Hold on LSPB" which
391        // makes the system behave as if the hold bit were set on all
392        // instructions; see User Guide, page 5-17.
393        let sinst = SymbolicInstruction {
394            operand_address: OperandAddress::direct(Address::from(u18!(0o200_156_u32))),
395            index: Unsigned6Bit::try_from(0o34_u8).unwrap(),
396            opcode: Opcode::Jpx,
397            configuration: config_value(0o36), // 036 octal = 30 decimal = 0b11110
398            held: true,
399        };
400        assert_eq!(&sinst.to_string(), "³⁶JPX₃₄ 200156");
401    }
402
403    #[test]
404    fn test_display_jpx_progex() {
405        // These cases are taken from the programming examples
406        // document (memo 6M-5780, July 23, 1958).
407
408        // Example from Program I, address 377 765, instruction word 36 06 01 377 751.
409        let sinst1 = SymbolicInstruction {
410            operand_address: OperandAddress::direct(Address::from(u18!(0o0_377_751))),
411            index: Unsigned6Bit::ONE,
412            opcode: Opcode::Jpx,
413            // 036 = 30 decimal = 0b11110, which is one's complement -1.
414            configuration: config_value(0o36),
415
416            // In the example program, we see the octal equivalent of
417            // the instruction, and the top bit is clearly not set, so
418            // instruction is not held.
419            held: false,
420        };
421        // In the Program I listing, there is no annotation indicating
422        // that the instruction is not held.  This memo is from 1958.
423        // Perhaps the conventions changed between then and the date
424        // of the User Handbook (1963).
425        //
426        // The actual example gives a configuration value of -1, but
427        // we have to choose a single way to render config values
428        // (i.e. choose one of -1 or 36 for bit pattern 0b11110).
429        // Compare for example test_display_rsx(), which points to an
430        // example where the configuration value is formatted the
431        // other way.
432        assert_eq!(&sinst1.to_string(), "ħ ³⁶JPX₁ 377751");
433
434        // Example from Program II ("Inchworm"), address 15, instruction word not stated,
435        let sinst2 = SymbolicInstruction {
436            operand_address: OperandAddress::direct(Address::from(u18!(0o0_377_752))),
437            index: Unsigned6Bit::try_from(3_u8).unwrap(),
438            opcode: Opcode::Jpx,
439            // In the Program II example, the configuration value is
440            // given as -7.  We're using octal 30 and believe that is
441            // equivalent.
442            configuration: Unsigned5Bit::try_from(0o30_u8).expect("valid test data"),
443            held: false,
444        };
445        // ħ here for the same reason as sinst1 disassembled above.
446        assert_eq!(&sinst2.to_string(), "ħ ³⁰JPX₃ 377752");
447    }
448
449    #[test]
450    fn test_display_rsx() {
451        // Example from Program II ("Inchworm"), address 377 755,
452        // instruction word 74 11 71 377 763.  Note the leading colon,
453        // indicating the hold bit.  We use 'h' instead since ':' is
454        // not actually part of the Lincoln Writer character set.
455        let sinst = SymbolicInstruction {
456            operand_address: OperandAddress::direct(Address::from(u18!(0o377_762))),
457            index: Unsigned6Bit::try_from(0o71_u8).unwrap(),
458            opcode: Opcode::Rsx,
459            configuration: config_value(0o34),
460            held: true, // this is signaled by the 'h'.
461        };
462        assert_eq!(&sinst.to_string(), "h ³⁴RSX₇₁ 377762"); // the 'h' indicates `held`
463    }
464
465    #[test]
466    fn test_display_ios() {
467        // This instruction appears in the listing for the "READER
468        // LEADER" in section 5-5.2 of the Users Handbook.
469        let sinst = SymbolicInstruction {
470            operand_address: OperandAddress::direct(Address::from(u18!(0o020_000_u32))),
471            index: Unsigned6Bit::try_from(0o52_u8).unwrap(),
472            // Bits 2.7 and 2.8 in the base address field distinguish
473            // the IOS and AOP variants of the OPR opcode.  If they're
474            // both zero, we're looking at IOS.
475            //
476            // This distinction and the bits used to make it are
477            // described in section 7-11 ("MISCELLANEOUS OPERATION
478            // CODES") of Volume 1 of the TX-2 Technical Manual.
479            opcode: Opcode::Opr,
480            configuration: config_value(0o1),
481            held: false,
482        };
483        assert_eq!(&sinst.to_string(), "¹IOS₅₂ 20000");
484    }
485
486    #[test]
487    fn test_disassemble_ios() {
488        // This instruction appears in the listing for the "READER
489        // LEADER" in section 5-5.2 of the Users Handbook.
490        let inst = Instruction::from(u36!(0o010_452_020_000));
491        assert_eq!(
492            SymbolicInstruction::try_from(&inst),
493            Ok(SymbolicInstruction {
494                operand_address: OperandAddress::direct(Address::from(u18!(0o020_000_u32))),
495                index: Unsigned6Bit::try_from(0o52_u8).unwrap(),
496                opcode: Opcode::Opr,
497                configuration: config_value(0o1),
498                held: false,
499            })
500        );
501    }
502    #[test]
503    fn test_assemble_ios() {
504        // This instruction appears in the listing for the "READER
505        // LEADER" in section 5-5.2 of the Users Handbook, in both
506        // symbolic and octal form.
507        assert_eq!(
508            Instruction::from(&SymbolicInstruction {
509                operand_address: OperandAddress::direct(Address::from(u18!(0o020_000_u32))),
510                index: Unsigned6Bit::try_from(0o52_u8).unwrap(),
511                opcode: Opcode::Opr,
512                configuration: config_value(0o1),
513                held: false,
514            })
515            .bits(),
516            u36!(0o010_452_020_000)
517        );
518    }
519}