assembler/
types.rs

1//! Fundamental types and errors.
2use std::ffi::OsStr;
3use std::fmt::{self, Display, Formatter};
4use std::io::Error as IoError;
5use std::path::PathBuf;
6
7use base::prelude::Address;
8
9use super::collections::OneOrMore;
10use super::memorymap::RcWordSource;
11use super::source::Source;
12use super::source::{LineAndColumn, WithLocation};
13use super::span::{Span, Spanned};
14use super::symbol::SymbolName;
15
16/// The TX-2 Users Handbook describes a section of source code
17/// beginning with an optional origin as a "block".  See, for example
18/// section 6-2.5 and section 6-3.4 (specifically, page 6-23).
19///
20/// This is not the same way in which "block" is used in more modern
21/// programming languages (for example, C), where a block is normally
22/// associated with a scope.  While TX-2's assembler does have scopes
23/// (in macro bodies) these are are a concept.
24///
25/// We assign blocks unique identifiers, which are values of type
26/// `BlockIdentifier`.
27#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
28pub struct BlockIdentifier(usize);
29
30impl From<usize> for BlockIdentifier {
31    fn from(value: usize) -> Self {
32        BlockIdentifier(value)
33    }
34}
35
36impl From<BlockIdentifier> for usize {
37    fn from(value: BlockIdentifier) -> usize {
38        value.0
39    }
40}
41
42impl BlockIdentifier {
43    pub fn previous_block(self) -> Option<BlockIdentifier> {
44        self.0.checked_sub(1).map(BlockIdentifier)
45    }
46}
47
48impl Display for BlockIdentifier {
49    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
50        write!(f, "block {}", self.0)
51    }
52}
53
54/// A hardware limit was exceeded.
55#[derive(Debug, PartialEq, Eq, Clone)]
56pub enum MachineLimitExceededFailure {
57    /// When an undefined Symex is used in an index context, it is
58    /// allocated a default value which has not yet been used.  When
59    /// there are no more available index registers, this allocation
60    /// fails and we signal this with
61    /// `MachineLimitExceededFailure::RanOutOfIndexRegisters`.
62    RanOutOfIndexRegisters(Span, SymbolName),
63    /// `BlockTooLarge` is used to report blocks whose length is not
64    /// representable in an 18-bit halfword, or whose length is
65    /// representable but whose start address wouild put the end of
66    /// the block outside physical memory.  Programs for which this
67    /// happens cannot fit into the TX-2's memory.
68    BlockTooLarge {
69        span: Span,
70        block_id: BlockIdentifier,
71        offset: usize,
72    },
73}
74
75impl Spanned for MachineLimitExceededFailure {
76    fn span(&self) -> Span {
77        match self {
78            MachineLimitExceededFailure::RanOutOfIndexRegisters(span, _)
79            | MachineLimitExceededFailure::BlockTooLarge { span, .. } => *span,
80        }
81    }
82}
83
84impl Display for MachineLimitExceededFailure {
85    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
86        match self {
87            MachineLimitExceededFailure::RanOutOfIndexRegisters(_span, name) => {
88                write!(
89                    f,
90                    "there are not enough index registers to assign one as the default for the symbol {name}"
91                )
92            }
93            MachineLimitExceededFailure::BlockTooLarge {
94                span: _,
95                block_id,
96                offset,
97            } => {
98                write!(
99                    f,
100                    "{block_id} is too large; offset {offset} does not fit in physical memory"
101                )
102            }
103        }
104    }
105}
106
107/// Failure to assemble a program.
108///
109/// This error indicates that the program we are trying to assemble is
110/// not valid.  Not all of these failure cases were detected by the
111/// TX-2's original assembler, M4.
112#[derive(Debug, PartialEq, Eq)]
113pub enum ProgramError {
114    /// A tag identifier was defined in two different places (within
115    /// the same scope).
116    InconsistentTag {
117        name: SymbolName,
118        span: Span,
119        msg: String,
120    },
121    /// A symbol's definition refers to the definition of the symbol
122    /// itself.  The Users Handbook states that M4 didn't reject this
123    /// but indicated that the output would be incorrect for this
124    /// case.
125    SymbolDefinitionLoop {
126        symbol_names: OneOrMore<SymbolName>,
127        span: Span,
128    },
129
130    /// Indicates that the assembler could not parse the input, or
131    /// that a syntactical element (such as "#") was used in a context
132    /// where it was not allowed.
133    SyntaxError { msg: String, span: Span },
134
135    /// As for [`MachineLimitExceededFailure::BlockTooLarge`].
136    BlockTooLong(Span, MachineLimitExceededFailure),
137
138    /// As for `MachineLimitExceededFailure::BlockTooLarge`, but
139    /// specifically for the RC block.
140    RcBlockTooLong(RcWordSource),
141
142    /// As for `MachineLimitExceededFailure::RanOutOfIndexRegisters`.
143    FailedToAssignIndexRegister(Span, SymbolName),
144}
145// TODO: either refactor `BlockTooLong`, `RcBlockTooLong` and
146// `FailedToAssignIndexRegister` so that they just refer to
147// `MachineLimitExceededFailure` or explain in the doc comment why
148// these are separate.
149
150impl Spanned for ProgramError {
151    fn span(&self) -> Span {
152        match self {
153            ProgramError::RcBlockTooLong(RcWordSource { span, .. })
154            | ProgramError::FailedToAssignIndexRegister(span, _)
155            | ProgramError::BlockTooLong(span, _)
156            | ProgramError::InconsistentTag { span, .. }
157            | ProgramError::SymbolDefinitionLoop { span, .. }
158            | ProgramError::SyntaxError { span, .. } => *span,
159        }
160    }
161}
162
163impl Display for ProgramError {
164    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
165        match self {
166            ProgramError::RcBlockTooLong(RcWordSource { kind, .. }) => {
167                write!(f, "RC-word block grew too long to allocate {kind}")
168            }
169            ProgramError::FailedToAssignIndexRegister(_span, symbol_name) => {
170                write!(
171                    f,
172                    "there are not enough index registers available to assign one for {symbol_name}"
173                )
174            }
175            ProgramError::BlockTooLong(_span, mle) => {
176                write!(f, "program block contains too many machine words: {mle}")
177            }
178            ProgramError::InconsistentTag { name, span: _, msg } => {
179                write!(f, "inconsistent definitions for tag {name}: {msg}")
180            }
181            ProgramError::SymbolDefinitionLoop {
182                symbol_names,
183                span: _,
184            } => {
185                let (head, tail) = symbol_names.as_parts();
186                write!(
187                    f,
188                    "symbol {head} is undefined because its definition forms a loop"
189                )?;
190                if !tail.is_empty() {
191                    write!(f, ": {head}")?;
192                    for name in tail {
193                        write!(f, "->{name}")?;
194                    }
195                }
196                Ok(())
197            }
198            ProgramError::SyntaxError { span: _, msg } => {
199                write!(f, "{msg}")
200            }
201        }
202    }
203}
204
205impl ProgramError {
206    pub(crate) fn into_assembler_failure(self, source_file_body: &Source<'_>) -> AssemblerFailure {
207        let span: Span = self.span();
208        let location: LineAndColumn = source_file_body.location_of(span.start);
209        AssemblerFailure::BadProgram(OneOrMore::new(WithLocation {
210            location,
211            inner: self,
212        }))
213    }
214}
215
216/// Indicates whether we are reading a file or writing to one.
217#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
218pub enum IoAction {
219    Read,
220    Write,
221}
222
223impl Display for IoAction {
224    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
225        f.write_str(match self {
226            IoAction::Read => "read",
227            IoAction::Write => "write",
228        })
229    }
230}
231
232/// Describes the target of an I/O operation.
233#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
234pub enum IoTarget {
235    File(PathBuf),
236}
237
238impl Display for IoTarget {
239    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
240        match self {
241            IoTarget::File(file_name) => {
242                f.write_str("file ")?;
243                write_os_string(f, file_name.as_os_str())
244            }
245        }
246    }
247}
248
249/// Describes a failure to perform I/O.
250#[derive(Debug)]
251pub struct IoFailed {
252    /// Indicates the nature of the I/O operation we were attempting.
253    pub action: IoAction,
254    /// Indiates what thing we were trying to perform the I/O operation on.
255    pub target: IoTarget,
256    /// Indicates what, specifically, went wrong.
257    pub error: IoError, // not cloneable, doesn't implement PartialEq
258}
259
260impl Display for IoFailed {
261    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
262        let IoFailed {
263            action,
264            target,
265            error,
266        } = self;
267        write!(f, "I/O error: {action} failed on {target}: {error}")
268    }
269}
270
271impl PartialEq<IoFailed> for IoFailed {
272    // We implement this ourselves since IoError does not implement PartialEq.
273    fn eq(&self, other: &IoFailed) -> bool {
274        self.action == other.action
275            && self.target == other.target
276            && self.error.to_string() == other.error.to_string()
277    }
278}
279
280impl Eq for IoFailed {}
281
282/// A failure to read the source and emit a binary.
283///
284/// This includes incorrect program input, but also other causes too.
285#[derive(Debug, PartialEq, Eq)]
286pub enum AssemblerFailure {
287    /// We encountered a bug in the assembler.
288    InternalError(String),
289    /// A program block is too large to be represented in the output
290    /// format (and hence in any case is too large to be loaded into
291    /// the TX-2 machine).
292    BadTapeBlock {
293        address: Address,
294        length: usize,
295        msg: String,
296    },
297    /// A failure to read or write data.
298    Io(IoFailed), // not cloneable
299    /// A syntax or semantic error in the program source.
300    BadProgram(OneOrMore<WithLocation<ProgramError>>),
301    /// The input program exceeds a machine limit.
302    MachineLimitExceeded(MachineLimitExceededFailure),
303}
304
305/// Writes an [`OsStr`] to stdout.
306///
307/// When the input contains unprintable (not validly encoded)
308/// characters, a warning is also written, but the function does not
309/// fail.
310fn write_os_string(f: &mut Formatter<'_>, s: &OsStr) -> Result<(), fmt::Error> {
311    match s.to_str() {
312        Some(unicode_name) => f.write_str(unicode_name),
313        None => write!(
314            f,
315            "{} (some non-Unicode characters changed to make it printable)",
316            s.to_string_lossy(),
317        ),
318    }
319}
320
321impl Display for AssemblerFailure {
322    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
323        match self {
324            AssemblerFailure::BadProgram(errors) => {
325                for e in errors.iter() {
326                    write!(f, "error in user program: {e}")?;
327                }
328                Ok(())
329            }
330            AssemblerFailure::InternalError(msg) => {
331                write!(f, "internal error: {msg}")
332            }
333            AssemblerFailure::BadTapeBlock {
334                address,
335                length,
336                msg,
337            } => {
338                write!(
339                    f,
340                    "bad tape block of length {length} words at address {address}: {msg}"
341                )
342            }
343            AssemblerFailure::Io(e) => write!(f, "{e}"),
344            AssemblerFailure::MachineLimitExceeded(fail) => {
345                write!(f, "machine limit exceeded: {fail}")
346            }
347        }
348    }
349}