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 ħ (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 "ħ") 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}