assembler/parser/
helpers.rs

1//! Convenience functions for the parser.
2use std::fmt::{Display, Write};
3use std::num::IntErrorKind;
4
5use base::prelude::*;
6
7use super::super::{ast::LiteralValue, manuscript::PunchCommand, state::NumeralMode};
8
9/// The sign of a number.
10#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
11pub(crate) enum Sign {
12    Plus,
13    Minus,
14}
15
16impl Display for Sign {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        f.write_char(match self {
19            Sign::Plus => '+',
20            Sign::Minus => '-',
21        })
22    }
23}
24
25/// Convert a string to an `Unsigned36Bit` in base `radix`.
26pub(super) fn make_u36(s: &str, radix: u32) -> Result<Unsigned36Bit, StringConversionFailed> {
27    match u64::from_str_radix(s, radix) {
28        Ok(n) => n.try_into().map_err(StringConversionFailed::Range),
29        Err(e) => match e.kind() {
30            IntErrorKind::Empty => Err(StringConversionFailed::EmptyInput),
31            IntErrorKind::InvalidDigit => match s.chars().find(|ch| ch.to_digit(radix).is_none()) {
32                Some(ch) => Err(StringConversionFailed::NotOctal(ch)),
33                None => {
34                    panic!(
35                        "at least one character of '{s}' is known to be invalid in base {radix}"
36                    );
37                }
38            },
39            IntErrorKind::PosOverflow => {
40                Err(StringConversionFailed::Range(ConversionFailed::TooLarge))
41            }
42            _ => unreachable!(),
43        },
44    }
45}
46
47#[test]
48fn test_make_u36() {
49    assert_eq!(Ok(u36!(0)), make_u36("0", 8));
50    assert_eq!(Ok(u36!(0)), make_u36("+0", 8));
51    assert_eq!(Ok(u36!(1)), make_u36("1", 8));
52    assert_eq!(Ok(u36!(1)), make_u36("1", 10));
53    assert_eq!(Ok(u36!(1)), make_u36("+1", 8));
54    assert_eq!(Ok(u36!(1)), make_u36("+1", 8));
55    assert!(make_u36("+1+1", 8).is_err());
56    assert!(make_u36("+-1", 8).is_err());
57    assert!(make_u36("-+1", 8).is_err());
58    assert!(make_u36("--1", 8).is_err());
59    assert!(make_u36("++1", 8).is_err());
60    assert!(make_u36("+ 1", 8).is_err());
61    assert!(make_u36("- 1", 8).is_err());
62    assert!(make_u36("18", 8).is_err());
63    assert!(make_u36("19", 8).is_err());
64    assert_eq!(Ok(u36!(19)), make_u36("19", 10));
65}
66
67/// Convert a string to an `Unsigned36Bit`.
68///
69/// # Combining `state` and `hasdot`
70///
71/// | `state`                | `hasdot` | Conversion Base |
72/// | ---------------------- | -------- | --------------- |
73/// | `NumeralMode::Octal`   | `false`  |  Octal          |
74/// | `NumeralMode::Decimal` | `false`  |  Decimal        |
75/// | `NumeralMode::Octal`   | `true`   |  Decimal        |
76/// | `NumeralMode::Decimal` | `true`   |  Octal          |
77///
78/// # Arguments
79///
80/// - `digits` - the octal or decimal digits
81/// - `hasdot` - if true, the input has a trailing dot (not in `digits`).
82/// - `state` - indicates whether the default mode is octal or decimal.
83///
84/// TODO: consider just having the caller call `make_u36()` directly.
85pub(crate) fn make_num(
86    digits: &str,
87    hasdot: bool,
88    state: NumeralMode,
89) -> Result<Unsigned36Bit, StringConversionFailed> {
90    make_u36(digits, state.radix(hasdot))
91}
92
93pub(super) fn punch_address(a: Option<LiteralValue>) -> Result<PunchCommand, String> {
94    match a {
95        None => Ok(PunchCommand(None)),
96        Some(literal) => {
97            let value = literal.value();
98            match Unsigned18Bit::try_from(value) {
99                Err(e) => Err(format!(
100                    "PUNCH address value {value:o} is not a valid address: {e}",
101                )),
102                Ok(halfword) => {
103                    let addr: Address = Address::from(halfword);
104                    if addr.mark_bit() == Unsigned18Bit::ZERO {
105                        Ok(PunchCommand(Some(addr)))
106                    } else {
107                        Err(format!(
108                            "PUNCH address value {addr:o} must not be a deferred address",
109                        ))
110                    }
111                }
112            }
113        }
114    }
115}
116
117/// Return the default value of the hold bit for the indicated opcode.
118///
119/// Some instructions are assembled with the hold bit automatically
120/// set.  These are `JPX`, `JNX`, `LDE`, `ITE`.  See Users Handbook,
121/// section 4-3.2 on page 4-5.
122pub(super) fn opcode_auto_hold_bit(opcode: Unsigned6Bit) -> u64 {
123    if matches!(u8::from(opcode), 0o06 | 0o07 | 0o20 | 0o40) {
124        1 << 35
125    } else {
126        0
127    }
128}
129
130/// Determine whether `name` is the name of a Arithmetic Element register.
131pub(super) fn is_arithmetic_element_register_name(name: &str) -> bool {
132    matches!(name, "A" | "B" | "C" | "D" | "E")
133}