assembler/
ast.rs

1//! Abstract syntax representation.   It's mostly not actually a tree.
2//!
3//! In the AST, terminology follows the TX-2 assembler's
4//! documentation, and this doesn't always match modern usage.  In
5//! particular, "block" is used to refer to a contiguously-allocated
6//! sequence of instructions which share an origin statement.  Such as
7//! the RC-block.  This is not the same as a block in a language like
8//! C, where "block" is also a declaration-scoping construct.
9//!
10//! Instead, in the TX-2 assembler, scopes are introduced by macro
11//! expansion.  So, a "block" may contain some instructions followed
12//! by a macro-expansion (which has an associated scope) which itself
13//! might contain a macro-expansion, with another scope.
14use std::borrow::Cow;
15use std::cmp::Ordering;
16use std::collections::BTreeMap;
17use std::error::Error;
18use std::fmt::{self, Display, Formatter, Octal, Write};
19use std::hash::Hash;
20use std::ops::{Shl, Shr};
21
22use tracing::{Level, event};
23
24use base::charset::{Script, subscript_char, superscript_char};
25use base::prelude::*;
26use base::u18;
27
28use super::collections::OneOrMore;
29use super::eval::{
30    Evaluate, EvaluationContext, EvaluationFailure, HereValue, ScopeIdentifier,
31    evaluate_elevated_symbol,
32};
33use super::glyph;
34use super::listing::{Listing, ListingLine};
35use super::manuscript::{MacroDefinition, MacroParameterBindings, MacroParameterValue};
36use super::memorymap::MemoryMap;
37use super::memorymap::RcAllocator;
38use super::memorymap::RcWordAllocationFailure;
39use super::memorymap::{RcWordKind, RcWordSource};
40use super::source::Source;
41use super::span::{Span, Spanned, span};
42use super::symbol::{InconsistentSymbolUse, SymbolContext, SymbolName};
43use super::symtab::{
44    BadSymbolDefinition, ExplicitDefinition, ExplicitSymbolTable, FinalSymbolDefinition,
45    FinalSymbolTable, FinalSymbolType, ImplicitSymbolTable, IndexRegisterAssigner, TagDefinition,
46    record_undefined_symbol_or_return_failure,
47};
48use super::types::{AssemblerFailure, BlockIdentifier, ProgramError};
49mod asteval;
50
51/// Indicates the action to be taken when a macro invocation does not
52/// specify a value for one of its dummy parameters.
53#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
54pub(crate) enum OnUnboundMacroParameter {
55    ElideReference,
56    SubstituteZero,
57}
58
59/// Records a reference to or definition of a symbol.
60#[derive(Debug, PartialEq, Eq, Clone)]
61pub(crate) enum SymbolUse {
62    Reference(SymbolContext),
63    Definition(ExplicitDefinition),
64}
65
66/// Allows updates to the values of RC-words.
67///
68/// We use this to separate the activities of selecting addresses for
69/// RC-words, and determining their values.
70pub(crate) trait RcUpdater {
71    fn update(&mut self, address: Address, value: Unsigned36Bit);
72}
73
74/// Eventually we will support symbolic expressions.
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub(crate) struct LiteralValue {
77    span: Span,
78    elevation: Script,
79    value: Unsigned36Bit,
80}
81
82impl LiteralValue {
83    pub(super) fn value(&self) -> Unsigned36Bit {
84        self.value << self.elevation.shift()
85    }
86
87    #[cfg(test)]
88    pub(super) fn unshifted_value(&self) -> Unsigned36Bit {
89        self.value
90    }
91}
92
93impl Spanned for LiteralValue {
94    fn span(&self) -> Span {
95        self.span
96    }
97}
98
99impl From<(Span, Script, Unsigned36Bit)> for LiteralValue {
100    fn from((span, elevation, value): (Span, Script, Unsigned36Bit)) -> LiteralValue {
101        LiteralValue {
102            span,
103            elevation,
104            value,
105        }
106    }
107}
108
109impl std::fmt::Display for LiteralValue {
110    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
111        let s = self.value.to_string();
112        format_elevated_chars(f, self.elevation, &s)
113    }
114}
115
116/// Write the name of a glyph with optional superscript / subscript
117/// indicator.
118fn write_glyph_name(f: &mut Formatter<'_>, elevation: Script, ch: char) -> fmt::Result {
119    let prefix: &'static str = match elevation {
120        Script::Sub => "sub_",
121        Script::Super => "sup_",
122        Script::Normal => "",
123    };
124    let name: &'static str = match glyph::name_from_glyph(ch) {
125        Some(n) => n,
126        None => {
127            panic!("There is no glyph name for character '{ch}'");
128        }
129    };
130    write!(f, "@{prefix}{name}@")
131}
132
133/// Convert a normal string to subscript or superscript (or leave it as-is).
134fn elevated_string(s: &str, elevation: Script) -> Cow<'_, str> {
135    match elevation {
136        Script::Normal => Cow::Borrowed(s),
137        Script::Super => Cow::Owned(
138            s.chars()
139                .map(|ch| superscript_char(ch).unwrap_or(ch))
140                .collect(),
141        ),
142        Script::Sub => Cow::Owned(
143            s.chars()
144                .map(|ch| subscript_char(ch).unwrap_or(ch))
145                .collect(),
146        ),
147    }
148}
149/// Format a string in super/sub/normal script, using `@...@` where necessary.
150fn format_elevated_chars(f: &mut Formatter<'_>, elevation: Script, s: &str) -> fmt::Result {
151    // TODO: do we really need both this and elevated_string?
152    match elevation {
153        Script::Normal => {
154            f.write_str(s)?;
155        }
156        Script::Super => {
157            for ch in s.chars() {
158                match superscript_char(ch) {
159                    Ok(superchar) => {
160                        f.write_char(superchar)?;
161                    }
162                    Err(_) => {
163                        write_glyph_name(f, elevation, ch)?;
164                    }
165                }
166            }
167        }
168        Script::Sub => {
169            for ch in s.chars() {
170                match subscript_char(ch) {
171                    Ok(sub) => {
172                        f.write_char(sub)?;
173                    }
174                    Err(_) => {
175                        write_glyph_name(f, elevation, ch)?;
176                    }
177                }
178            }
179        }
180    }
181    Ok(())
182}
183
184/// The Users Handbook specifies that the operators are the four
185/// arithmetic operators (+-×/) and the logical operators ∧ (AND), ∨
186/// (OR), and a circled ∨ meaning XOR.
187#[derive(Debug, Clone, Copy, PartialEq, Eq)]
188pub(crate) enum Operator {
189    Add,
190    LogicalAnd,
191    LogicalOr, // "union" in the Users Handbook
192    Multiply,
193    Subtract,
194    Divide,
195}
196
197impl std::fmt::Display for Operator {
198    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
199        f.write_char(match self {
200            Operator::Add => '+',
201            Operator::LogicalAnd => '∧',
202            Operator::LogicalOr => '∨',
203            Operator::Multiply => '\u{00D7}',
204            Operator::Subtract => '-',
205            Operator::Divide => '/',
206        })
207    }
208}
209
210#[derive(Debug, Clone, PartialEq, Eq)]
211pub(crate) struct SignedAtom {
212    pub(super) negated: bool,
213    pub(super) span: Span,
214    pub(super) magnitude: Atom,
215}
216
217impl From<Atom> for SignedAtom {
218    fn from(magnitude: Atom) -> Self {
219        Self {
220            negated: false,
221            span: magnitude.span(),
222            magnitude,
223        }
224    }
225}
226
227impl SignedAtom {
228    fn symbol_uses(
229        &self,
230        block_id: BlockIdentifier,
231        block_offset: Unsigned18Bit,
232    ) -> impl Iterator<Item = Result<(SymbolName, Span, SymbolUse), InconsistentSymbolUse>> + use<>
233    {
234        self.magnitude.symbol_uses(block_id, block_offset)
235    }
236
237    fn substitute_macro_parameters(
238        &self,
239        param_values: &MacroParameterBindings,
240        on_missing: OnUnboundMacroParameter,
241        macros: &BTreeMap<SymbolName, MacroDefinition>,
242    ) -> Option<SignedAtom> {
243        self.magnitude
244            .substitute_macro_parameters(param_values, on_missing, macros)
245            .map(|magnitude| SignedAtom {
246                magnitude,
247                ..self.clone()
248            })
249    }
250
251    fn allocate_rc_words<R: RcAllocator>(
252        &mut self,
253        explicit_symtab: &mut ExplicitSymbolTable,
254        implicit_symtab: &mut ImplicitSymbolTable,
255        rc_allocator: &mut R,
256    ) -> Result<(), RcWordAllocationFailure> {
257        self.magnitude
258            .allocate_rc_words(explicit_symtab, implicit_symtab, rc_allocator)
259    }
260}
261
262impl Spanned for SignedAtom {
263    fn span(&self) -> Span {
264        self.span
265    }
266}
267
268impl std::fmt::Display for SignedAtom {
269    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
270        if self.negated {
271            write!(f, "-{}", self.magnitude)
272        } else {
273            write!(f, "{}", self.magnitude)
274        }
275    }
276}
277
278/// Represents an arithmetic expression.
279///
280/// In the TX-2's M4 assembly language, arithmetic expressions are
281/// constants.  In other words, the assembler evaluates them to a
282/// specific 36-bit value which is emitted into the output.
283#[derive(Debug, Clone, PartialEq, Eq)]
284pub(crate) struct ArithmeticExpression {
285    pub(crate) first: SignedAtom,
286    pub(crate) tail: Vec<(Operator, SignedAtom)>,
287}
288
289impl std::fmt::Display for ArithmeticExpression {
290    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
291        write!(f, "{}", self.first)?;
292        for (op, atom) in &self.tail {
293            write!(f, "{op}{atom}")?;
294        }
295        Ok(())
296    }
297}
298
299impl From<SignedAtom> for ArithmeticExpression {
300    fn from(a: SignedAtom) -> ArithmeticExpression {
301        ArithmeticExpression {
302            first: a,
303            tail: Vec::new(),
304        }
305    }
306}
307
308impl From<Atom> for ArithmeticExpression {
309    fn from(a: Atom) -> ArithmeticExpression {
310        ArithmeticExpression::from(SignedAtom::from(a))
311    }
312}
313
314impl From<SymbolOrLiteral> for ArithmeticExpression {
315    fn from(value: SymbolOrLiteral) -> Self {
316        ArithmeticExpression::from(Atom::from(value))
317    }
318}
319
320impl Spanned for ArithmeticExpression {
321    fn span(&self) -> Span {
322        let start = self.first.span().start;
323        let end = self
324            .tail
325            .last()
326            .map_or(self.first.span().end, |(_op, atom)| atom.span().end);
327        span(start..end)
328    }
329}
330
331impl ArithmeticExpression {
332    pub(super) fn with_tail(
333        first: SignedAtom,
334        tail: Vec<(Operator, SignedAtom)>,
335    ) -> ArithmeticExpression {
336        ArithmeticExpression { first, tail }
337    }
338
339    fn symbol_uses(
340        &self,
341        block_id: BlockIdentifier,
342        block_offset: Unsigned18Bit,
343    ) -> impl Iterator<Item = Result<(SymbolName, Span, SymbolUse), InconsistentSymbolUse>> + use<>
344    {
345        let mut result = Vec::with_capacity(1 + self.tail.len());
346        result.extend(self.first.symbol_uses(block_id, block_offset));
347        result.extend(
348            self.tail
349                .iter()
350                .flat_map(|(_op, x)| x.symbol_uses(block_id, block_offset)),
351        );
352        result.into_iter()
353    }
354
355    fn eval_binop(left: Unsigned36Bit, binop: Operator, right: Unsigned36Bit) -> Unsigned36Bit {
356        match binop {
357            Operator::Add => match left.checked_add(right) {
358                Some(result) => result,
359                None => {
360                    // TODO: checked_add doesn't currently match the
361                    // operation of the TX-2 ADD instruction with
362                    // respect to (for example) overflow.  See
363                    // examples 4 and 5 for the ADD instruciton in the
364                    // Users Handbook, for instance.
365                    //
366                    // We also come here for cases like 4 + -2,
367                    // because (in the context of this function) -2
368                    // appears to be a large unsigned number.
369                    todo!(
370                        "{left:>012o}+{right:>012o} overflowed; please fix https://github.com/TX-2/TX-2-simulator/issues/146"
371                    )
372                }
373            },
374            Operator::Subtract => match left.checked_sub(right) {
375                Some(result) => result,
376                None => {
377                    todo!(
378                        "{left:>012o}-{right:>012o} overflowed; please fix https://github.com/TX-2/TX-2-simulator/issues/146"
379                    )
380                }
381            },
382            Operator::Multiply => match left.checked_mul(right) {
383                Some(result) => result,
384                None => {
385                    todo!("multiplication overflow occurred but this is not implemented")
386                }
387            },
388            Operator::Divide => {
389                let sleft: Signed36Bit = left.reinterpret_as_signed();
390                let sright: Signed36Bit = right.reinterpret_as_signed();
391                match sleft.checked_div(sright) {
392                    Some(result) => result.reinterpret_as_unsigned(),
393                    None => {
394                        if sright.is_positive_zero() {
395                            !left
396                        } else if sright.is_negative_zero() {
397                            left
398                        } else {
399                            unreachable!("division overflow occurred but RHS is not zero")
400                        }
401                    }
402                }
403            }
404            Operator::LogicalAnd => left.and(right.into()),
405            Operator::LogicalOr => left.bitor(right.into()),
406        }
407    }
408
409    fn substitute_macro_parameters(
410        &self,
411        param_values: &MacroParameterBindings,
412        on_missing: OnUnboundMacroParameter,
413        macros: &BTreeMap<SymbolName, MacroDefinition>,
414    ) -> Option<ArithmeticExpression> {
415        match self
416            .first
417            .substitute_macro_parameters(param_values, on_missing, macros)
418        {
419            None => None,
420            Some(first) => {
421                let mut tail: Vec<(Operator, SignedAtom)> = Vec::with_capacity(self.tail.len());
422                for (op, atom) in &self.tail {
423                    match atom.substitute_macro_parameters(param_values, on_missing, macros) {
424                        Some(atom) => {
425                            tail.push((*op, atom));
426                        }
427                        None => {
428                            return None;
429                        }
430                    }
431                }
432                Some(ArithmeticExpression { first, tail })
433            }
434        }
435    }
436
437    fn allocate_rc_words<R: RcAllocator>(
438        &mut self,
439        explicit_symtab: &mut ExplicitSymbolTable,
440        implicit_symtab: &mut ImplicitSymbolTable,
441        rc_allocator: &mut R,
442    ) -> Result<(), RcWordAllocationFailure> {
443        self.first
444            .allocate_rc_words(explicit_symtab, implicit_symtab, rc_allocator)?;
445        for (_op, atom) in &mut self.tail {
446            atom.allocate_rc_words(explicit_symtab, implicit_symtab, rc_allocator)?;
447        }
448        Ok(())
449    }
450}
451
452/// An expression used to specify the configuration bits of an instruction.
453///
454/// A configuration syllable can be specified by putting it in a
455/// superscript, or by putting it in normal script after a ‖ symbol
456/// (‖x or ‖2, for example).  This is described in section 6-2.1 of
457/// the Users Handbook.
458///
459/// In the description of the parts of an instruction (section 6-2.1,
460/// "INSTRUCTION WORDS" of the Users Handbook) we see the
461/// specification that the configuration syllable cannot contain any
462/// spaces.  But this doesn't prevent the config syllable containing,
463/// say, an arithmetic expression.  Indeed, Leonard Kleinrock's
464/// program for network simulation does exactly that (by using a
465/// negated symbol as a configuration value).
466///
467#[derive(Debug, Clone, PartialEq, Eq)]
468pub(crate) struct ConfigValue {
469    /// Indicates that the value was already in superscript.
470    pub(crate) already_superscript: bool,
471    /// Holds the configuration syllable value.
472    pub(crate) expr: ArithmeticExpression,
473}
474
475impl ConfigValue {
476    fn symbol_uses(
477        &self,
478        block_id: BlockIdentifier,
479        block_offset: Unsigned18Bit,
480    ) -> impl Iterator<Item = Result<(SymbolName, Span, SymbolUse), InconsistentSymbolUse>> + use<>
481    {
482        self.expr
483            .symbol_uses(block_id, block_offset)
484            .map(|r| match r {
485                Ok((name, span, _ignore_symbol_use)) => Ok((
486                    name,
487                    span,
488                    SymbolUse::Reference(SymbolContext::configuration(span)),
489                )),
490                Err(e) => Err(e),
491            })
492    }
493
494    fn substitute_macro_parameters(
495        &self,
496        param_values: &MacroParameterBindings,
497        on_missing: OnUnboundMacroParameter,
498        macros: &BTreeMap<SymbolName, MacroDefinition>,
499    ) -> Option<ConfigValue> {
500        self.expr
501            .substitute_macro_parameters(param_values, on_missing, macros)
502            .map(|expr| ConfigValue {
503                expr,
504                already_superscript: self.already_superscript,
505            })
506    }
507
508    fn allocate_rc_words<R: RcAllocator>(
509        &mut self,
510        explicit_symtab: &mut ExplicitSymbolTable,
511        implicit_symtab: &mut ImplicitSymbolTable,
512        rc_allocator: &mut R,
513    ) -> Result<(), RcWordAllocationFailure> {
514        self.expr
515            .allocate_rc_words(explicit_symtab, implicit_symtab, rc_allocator)
516    }
517}
518
519impl Spanned for ConfigValue {
520    fn span(&self) -> Span {
521        self.expr.span()
522    }
523}
524
525#[derive(Debug, Clone, PartialEq, Eq)]
526pub(crate) struct RegistersContaining(OneOrMore<RegisterContaining>);
527
528impl RegistersContaining {
529    pub(super) fn from_words(words: OneOrMore<RegisterContaining>) -> RegistersContaining {
530        Self(words)
531    }
532
533    pub(super) fn words(&self) -> impl Iterator<Item = &RegisterContaining> {
534        self.0.iter()
535    }
536
537    pub(crate) fn words_mut(&mut self) -> impl Iterator<Item = &mut RegisterContaining> {
538        self.0.iter_mut()
539    }
540
541    fn symbol_uses(
542        &self,
543        block_id: BlockIdentifier,
544        block_offset: Unsigned18Bit,
545    ) -> impl Iterator<Item = Result<(SymbolName, Span, SymbolUse), InconsistentSymbolUse>> + use<'_>
546    {
547        self.0
548            .iter()
549            .flat_map(move |rc| rc.symbol_uses(block_id, block_offset))
550    }
551
552    fn substitute_macro_parameters(
553        &self,
554        param_values: &MacroParameterBindings,
555        on_missing: OnUnboundMacroParameter,
556        macros: &BTreeMap<SymbolName, MacroDefinition>,
557    ) -> Option<RegistersContaining> {
558        // TODO: this implementation probably requires more thought.
559        // At the moment, if the contents of {...} yield any single
560        // item that gets omitted because it references an unbound
561        // macro paramter, then the whole {...} (and therefore
562        // anything containing it) gets omitted.
563        //
564        // This may not match the required behaviour of the TX-2's M4
565        // assembler, which might instead just omit that RC-word.
566        // However, if we switch to that option, then this could
567        // create a situation in which {...} results in zero words of
568        // the RC-block being reserved.  In that case, what is the
569        // resulting numerical value of the {...} expression?  If it
570        // actually does reserve zero words, then it means that two or
571        // more instances of {...} could resolve to the same address,
572        // and that is likely not intended.  It wold also mean trouble
573        // for the current implementation of the Spanned trait for
574        // RegistersContaining.
575        let tmp_rc: OneOrMore<Option<RegisterContaining>> = self
576            .0
577            .map(|rc| rc.substitute_macro_parameters(param_values, on_missing, macros));
578        if tmp_rc.iter().all(Option::is_some) {
579            Some(RegistersContaining(tmp_rc.into_map(|maybe_rc| {
580                maybe_rc.expect("we already checked this wasn't None")
581            })))
582        } else {
583            None
584        }
585    }
586
587    fn allocate_rc_words<R: RcAllocator>(
588        &mut self,
589        span: Span,
590        explicit_symtab: &mut ExplicitSymbolTable,
591        implicit_symtab: &mut ImplicitSymbolTable,
592        rc_allocator: &mut R,
593    ) -> Result<(), RcWordAllocationFailure> {
594        let source = RcWordSource {
595            span,
596            kind: RcWordKind::Braces,
597        };
598        for rc in self.words_mut() {
599            *rc = rc.clone().assign_rc_word(
600                source.clone(),
601                explicit_symtab,
602                implicit_symtab,
603                rc_allocator,
604            )?;
605        }
606        Ok(())
607    }
608}
609
610impl Spanned for RegistersContaining {
611    fn span(&self) -> Span {
612        use chumsky::span::Span;
613        let mut it = self.0.iter();
614        match it.next() {
615            Some(rc) => it.fold(rc.span(), |acc, rc| acc.union(rc.span())),
616            None => {
617                unreachable!(
618                    "invariant broken: RegistersContaining contains no RegisterContaining instances"
619                )
620            }
621        }
622    }
623}
624
625/// An RC-word.
626///
627/// See section 6-2.6 ("RC WORDS - RC BLOCK").
628///
629/// Section 6-4.7 ("Use of Macro Instructions") states that macro
630/// expansion may occur inside an RC-word (and expand to more than one
631/// word in the output binary) but this is not yet supported.
632#[derive(Debug, Clone, PartialEq, Eq)]
633pub(crate) enum RegisterContaining {
634    Unallocated(Box<TaggedProgramInstruction>),
635    Allocated(Address, Box<TaggedProgramInstruction>),
636}
637
638impl From<TaggedProgramInstruction> for RegisterContaining {
639    fn from(inst: TaggedProgramInstruction) -> Self {
640        RegisterContaining::Unallocated(Box::new(inst))
641    }
642}
643
644impl Spanned for RegisterContaining {
645    fn span(&self) -> Span {
646        match self {
647            RegisterContaining::Unallocated(b) | RegisterContaining::Allocated(_, b) => b.span(),
648        }
649    }
650}
651
652impl RegisterContaining {
653    fn instruction(&self) -> &TaggedProgramInstruction {
654        match self {
655            RegisterContaining::Unallocated(tpi) | RegisterContaining::Allocated(_, tpi) => tpi,
656        }
657    }
658
659    fn symbol_uses(
660        &self,
661        block_id: BlockIdentifier,
662        block_offset: Unsigned18Bit,
663    ) -> impl Iterator<Item = Result<(SymbolName, Span, SymbolUse), InconsistentSymbolUse>> + use<'_>
664    {
665        let mut result: Vec<Result<_, _>> = Vec::new();
666        for r in self.instruction().symbol_uses(block_id, block_offset) {
667            match r {
668                Ok((name, span, symbol_definition)) => {
669                    match symbol_definition {
670                        def @ SymbolUse::Reference(_) => {
671                            result.push(Ok((name, span, def)));
672                        }
673                        SymbolUse::Definition(ExplicitDefinition::Tag { .. }) => {
674                            // Here we have a tag definition inside an
675                            // RC-word.  Therefore the passed-in value of
676                            // `block_id` is wrong (it refers to the block
677                            // containing the RC-word, not to the RC-block)
678                            // and the offset is similarly wrong.
679                            //
680                            // Therefore we will process these uses of symbols
681                            // at the time we allocate addresses for RC-block
682                            // words.
683                        }
684                        SymbolUse::Definition(ExplicitDefinition::Origin(_, _)) => {
685                            unreachable!(
686                                "Found origin {name} inside an RC-word; the parser should have rejected this."
687                            );
688                        }
689                        SymbolUse::Definition(_) => {
690                            // e.g. we have an input like
691                            //
692                            // { X = 2 }
693                            //
694                            //
695                            // Ideally we would issue an error for
696                            // this, but since this function cannot
697                            // fail, it's better to do that at the
698                            // time we parse the RC-word reference
699                            // (thus eliminating this case).
700                            //
701                            // When working on this case we should
702                            // figure out if an equality is allowed
703                            // inside a macro expansion.
704                            panic!(
705                                "Found unexpected definition of {name} inside RC-word reference at {span:?}"
706                            );
707                        }
708                    }
709                }
710                Err(e) => {
711                    result.push(Err(e));
712                }
713            }
714        }
715        result.into_iter()
716    }
717
718    fn substitute_macro_parameters(
719        &self,
720        param_values: &MacroParameterBindings,
721        on_missing: OnUnboundMacroParameter,
722        macros: &BTreeMap<SymbolName, MacroDefinition>,
723    ) -> Option<RegisterContaining> {
724        match self {
725            RegisterContaining::Unallocated(tagged_program_instruction) => {
726                tagged_program_instruction
727                    .substitute_macro_parameters(param_values, on_missing, macros)
728                    .map(|tagged_program_instruction| {
729                        RegisterContaining::Unallocated(Box::new(tagged_program_instruction))
730                    })
731            }
732            RegisterContaining::Allocated(_address, _tagged_program_instruction) => {
733                // One reason we don't support this is because if we
734                // twice instantiate a macro which contains {...} or a
735                // pipe construct, then both of those RC-words would
736                // have the same address, and this is likely not
737                // intended.  It's certainly user-surprising.
738                //
739                // The second reason we don't support this (and the
740                // reason why we don't need to issue an error message
741                // for the user) is that the assembler implementation
742                // does in fact perform macro-expansion before
743                // RC-words are allocated.
744                unreachable!(
745                    "macro expansion must be completed before any RC-block addresses are allocated"
746                )
747            }
748        }
749    }
750
751    fn assign_rc_word<R: RcAllocator>(
752        self,
753        source: RcWordSource,
754        explicit_symtab: &mut ExplicitSymbolTable,
755        implicit_symtab: &mut ImplicitSymbolTable,
756        rc_allocator: &mut R,
757    ) -> Result<RegisterContaining, RcWordAllocationFailure> {
758        match self {
759            RegisterContaining::Unallocated(mut tpibox) => {
760                let address: Address = rc_allocator.allocate(source, Unsigned36Bit::ZERO)?;
761                for tag in &tpibox.tags {
762                    eprintln!(
763                        "assigning RC-word at address {address} serves as defnition of tag {}",
764                        &tag.name
765                    );
766                    implicit_symtab.remove(&tag.name);
767                    let new_tag_definition = TagDefinition::Resolved {
768                        span: tag.span,
769                        address,
770                    };
771                    match explicit_symtab.define(
772                        tag.name.clone(),
773                        ExplicitDefinition::Tag(new_tag_definition.clone()),
774                    ) {
775                        Ok(()) => (),
776                        Err(BadSymbolDefinition {
777                            symbol_name,
778                            span,
779                            existing,
780                            proposed: _,
781                        }) => {
782                            return Err(RcWordAllocationFailure::InconsistentTag {
783                                tag_name: symbol_name,
784                                span,
785                                explanation: format!(
786                                    "previous definition {existing} is incompatible with new definition {new_tag_definition}"
787                                ),
788                            });
789                        }
790                    }
791                }
792                tpibox.allocate_rc_words(explicit_symtab, implicit_symtab, rc_allocator)?;
793                let tpi: Box<TaggedProgramInstruction> = tpibox;
794                Ok(RegisterContaining::Allocated(address, tpi))
795            }
796            other @ RegisterContaining::Allocated(..) => Ok(other),
797        }
798    }
799}
800
801/// A component of an arithmetic expression.
802///
803/// A symbol, numeric literal, address of an RC-word, or a
804/// parenthesised arithmetic expression.
805#[derive(Debug, Clone, PartialEq, Eq)]
806pub(crate) enum Atom {
807    SymbolOrLiteral(SymbolOrLiteral),
808    Parens(Span, Script, Box<ArithmeticExpression>),
809    RcRef(Span, RegistersContaining),
810}
811
812impl From<(Span, Script, SymbolName)> for Atom {
813    fn from((span, script, name): (Span, Script, SymbolName)) -> Self {
814        Atom::SymbolOrLiteral(SymbolOrLiteral::Symbol(script, name, span))
815    }
816}
817
818impl From<SymbolOrLiteral> for Atom {
819    fn from(value: SymbolOrLiteral) -> Self {
820        Atom::SymbolOrLiteral(value)
821    }
822}
823
824impl Atom {
825    fn symbol_uses(
826        &self,
827        block_id: BlockIdentifier,
828        block_offset: Unsigned18Bit,
829    ) -> impl Iterator<Item = Result<(SymbolName, Span, SymbolUse), InconsistentSymbolUse>> + use<>
830    {
831        let mut result: Vec<Result<_, _>> = Vec::with_capacity(1);
832        match self {
833            Atom::SymbolOrLiteral(SymbolOrLiteral::Symbol(script, name, span)) => {
834                result.push(Ok((
835                    name.clone(),
836                    *span,
837                    SymbolUse::Reference(SymbolContext::from((script, *span))),
838                )));
839            }
840            Atom::SymbolOrLiteral(SymbolOrLiteral::Literal(_) | SymbolOrLiteral::Here(_, _)) => (),
841            Atom::Parens(_span, _script, expr) => {
842                result.extend(expr.symbol_uses(block_id, block_offset));
843            }
844            Atom::RcRef(_span, rc_words) => {
845                result.extend(rc_words.symbol_uses(block_id, block_offset));
846            }
847        }
848        result.into_iter()
849    }
850
851    fn substitute_macro_parameters(
852        &self,
853        param_values: &MacroParameterBindings,
854        on_missing: OnUnboundMacroParameter,
855        macros: &BTreeMap<SymbolName, MacroDefinition>,
856    ) -> Option<Atom> {
857        match self {
858            Atom::SymbolOrLiteral(symbol_or_literal) => {
859                match symbol_or_literal.substitute_macro_parameters(param_values, on_missing) {
860                    SymbolSubstitution::AsIs(symbol_or_literal) => {
861                        Some(Atom::SymbolOrLiteral(symbol_or_literal))
862                    }
863                    SymbolSubstitution::Hit(span, script, arithmetic_expression) => Some(
864                        Atom::Parens(span, script, Box::new(arithmetic_expression.clone())),
865                    ),
866                    SymbolSubstitution::Omit => {
867                        // The parameter was not set, and this atom is
868                        // being used in a context where omitted
869                        // parameters cause the affected instruction
870                        // to be omitted.  That is, this expression is
871                        // not on the right-hand-side of an equality.
872                        None
873                    }
874                    SymbolSubstitution::Zero(span) => {
875                        Some(Atom::SymbolOrLiteral(SymbolOrLiteral::Literal(
876                            LiteralValue {
877                                span,
878                                // Since the value is zero the elevation actually doesn't matter.
879                                elevation: Script::Normal,
880                                value: Unsigned36Bit::ZERO,
881                            },
882                        )))
883                    }
884                }
885            }
886            Atom::Parens(span, script, arithmetic_expression) => arithmetic_expression
887                .substitute_macro_parameters(param_values, on_missing, macros)
888                .map(|arithmetic_expression| {
889                    Atom::Parens(*span, *script, Box::new(arithmetic_expression))
890                }),
891            Atom::RcRef(span, registers_containing) => registers_containing
892                .substitute_macro_parameters(param_values, on_missing, macros)
893                .map(|registers_containing| Atom::RcRef(*span, registers_containing)),
894        }
895    }
896
897    fn allocate_rc_words<R: RcAllocator>(
898        &mut self,
899        explicit_symtab: &mut ExplicitSymbolTable,
900        implicit_symtab: &mut ImplicitSymbolTable,
901        rc_allocator: &mut R,
902    ) -> Result<(), RcWordAllocationFailure> {
903        match self {
904            Atom::SymbolOrLiteral(thing) => {
905                thing.allocate_rc_words(explicit_symtab, implicit_symtab, rc_allocator)
906            }
907            Atom::Parens(_, _, expr) => {
908                expr.allocate_rc_words(explicit_symtab, implicit_symtab, rc_allocator)
909            }
910            Atom::RcRef(span, rc) => {
911                rc.allocate_rc_words(*span, explicit_symtab, implicit_symtab, rc_allocator)
912            }
913        }
914    }
915}
916
917impl Spanned for Atom {
918    fn span(&self) -> Span {
919        match self {
920            Atom::SymbolOrLiteral(value) => value.span(),
921            Atom::Parens(span, _script, _bae) => *span,
922            Atom::RcRef(span, _) => *span,
923        }
924    }
925}
926
927impl From<LiteralValue> for Atom {
928    fn from(literal: LiteralValue) -> Atom {
929        Atom::SymbolOrLiteral(SymbolOrLiteral::Literal(literal))
930    }
931}
932
933impl From<(Span, Script, Unsigned36Bit)> for Atom {
934    fn from((span, script, v): (Span, Script, Unsigned36Bit)) -> Atom {
935        Atom::from(LiteralValue::from((span, script, v)))
936    }
937}
938
939impl std::fmt::Display for Atom {
940    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
941        match self {
942            Atom::SymbolOrLiteral(value) => write!(f, "{value}"),
943            Atom::Parens(_span, script, expr) => elevated_string(&expr.to_string(), *script).fmt(f),
944            Atom::RcRef(_span, _rc_reference) => {
945                // The RcRef doesn't itself record the content of the
946                // {...} because that goes into the rc-block itself.
947                write!(f, "{{...}}")
948            }
949        }
950    }
951}
952
953/// Indicates how some particular macro parameter should be processed.
954#[derive(Debug, Clone, PartialEq, Eq)]
955pub(crate) enum SymbolSubstitution<T> {
956    AsIs(T),
957    Hit(Span, Script, ArithmeticExpression),
958    Omit,
959    Zero(Span),
960}
961
962/// A symbol name or a numeric literal, or `#`.
963///
964/// A `#` represents the address of the program word whose value the
965/// assembler is currently trying to determine.
966#[derive(Debug, Clone, PartialEq, Eq)]
967pub(crate) enum SymbolOrLiteral {
968    Symbol(Script, SymbolName, Span),
969    Literal(LiteralValue),
970    Here(Script, Span),
971}
972
973impl SymbolOrLiteral {
974    fn symbol_uses(
975        &self,
976    ) -> impl Iterator<Item = Result<(SymbolName, Span, SymbolUse), InconsistentSymbolUse>> + use<>
977    {
978        let mut result = Vec::with_capacity(1);
979        match self {
980            SymbolOrLiteral::Here(_, _) | SymbolOrLiteral::Literal(_) => (),
981            SymbolOrLiteral::Symbol(script, name, span) => {
982                let context: SymbolContext = (script, *span).into();
983                let sym_use: SymbolUse = SymbolUse::Reference(context);
984                result.push(Ok((name.clone(), *span, sym_use)));
985            }
986        }
987        result.into_iter()
988    }
989
990    fn substitute_macro_parameters(
991        &self,
992        param_values: &MacroParameterBindings,
993        on_missing: OnUnboundMacroParameter,
994    ) -> SymbolSubstitution<SymbolOrLiteral> {
995        match self {
996            SymbolOrLiteral::Symbol(_script, symbol_name, _span) => {
997                match param_values.get(symbol_name) {
998                    Some((
999                        span,
1000                        Some(MacroParameterValue::Value(script, arithmetic_expression)),
1001                    )) => {
1002                        // symbol_name was a parameter name, and the
1003                        // macro invocation specified it, so
1004                        // substitute it.
1005                        SymbolSubstitution::Hit(*span, *script, arithmetic_expression.clone())
1006                    }
1007                    Some((span, None)) => {
1008                        // symbol_name was a parameter name, but the
1009                        // macro invocation did not specify it, so we
1010                        // either elide the instruction or behave if
1011                        // it is zero, according to on_missing.
1012                        match on_missing {
1013                            OnUnboundMacroParameter::ElideReference => SymbolSubstitution::Omit,
1014                            OnUnboundMacroParameter::SubstituteZero => {
1015                                SymbolSubstitution::Zero(*span)
1016                            }
1017                        }
1018                    }
1019                    None => {
1020                        // symbol_name is not a macro parameter.
1021                        SymbolSubstitution::AsIs(self.clone())
1022                    }
1023                }
1024            }
1025            SymbolOrLiteral::Literal(literal) => {
1026                SymbolSubstitution::AsIs(SymbolOrLiteral::Literal(literal.clone()))
1027            }
1028            SymbolOrLiteral::Here(script, span) => {
1029                SymbolSubstitution::AsIs(SymbolOrLiteral::Here(*script, *span))
1030            }
1031        }
1032    }
1033
1034    fn allocate_rc_words<R: RcAllocator>(
1035        &mut self,
1036        _explicit_symtab: &ExplicitSymbolTable,
1037        _implicit_symtab: &mut ImplicitSymbolTable,
1038        _rc_allocator: &mut R,
1039    ) -> Result<(), RcWordAllocationFailure> {
1040        // We use Result for consistency with the allocate_rc_words()
1041        // methods of other AST elements.
1042        #![allow(clippy::unnecessary_wraps)]
1043        // SymbolOrliteral doesn't contain anything that would reserve
1044        // RC-words, so there is nothing to do here.
1045        let _ = self; // placate the unused_self Clippy lint.
1046        Ok(())
1047    }
1048}
1049
1050impl Spanned for SymbolOrLiteral {
1051    fn span(&self) -> Span {
1052        match self {
1053            SymbolOrLiteral::Literal(literal_value) => literal_value.span,
1054            SymbolOrLiteral::Symbol(_, _, span) | SymbolOrLiteral::Here(_, span) => *span,
1055        }
1056    }
1057}
1058
1059impl Display for SymbolOrLiteral {
1060    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1061        match self {
1062            SymbolOrLiteral::Here(script, _span) => match script {
1063                Script::Super => f.write_str("@super_hash@"),
1064                Script::Normal => f.write_char('#'),
1065                Script::Sub => f.write_str("@sub_hash@"),
1066            },
1067            SymbolOrLiteral::Symbol(script, name, _) => {
1068                elevated_string(&name.to_string(), *script).fmt(f)
1069            }
1070            SymbolOrLiteral::Literal(value) => value.fmt(f),
1071        }
1072    }
1073}
1074
1075#[derive(Debug, PartialEq, Eq, Clone)]
1076pub(crate) struct SpannedSymbolOrLiteral {
1077    pub(crate) item: SymbolOrLiteral,
1078    pub(crate) span: Span,
1079}
1080
1081impl SpannedSymbolOrLiteral {
1082    fn substitute_macro_parameters(
1083        &self,
1084        param_values: &MacroParameterBindings,
1085        on_missing: OnUnboundMacroParameter,
1086    ) -> SymbolSubstitution<SpannedSymbolOrLiteral> {
1087        match self
1088            .item
1089            .substitute_macro_parameters(param_values, on_missing)
1090        {
1091            SymbolSubstitution::AsIs(item) => SymbolSubstitution::AsIs(SpannedSymbolOrLiteral {
1092                item,
1093                span: self.span,
1094            }),
1095            SymbolSubstitution::Hit(span, script, arithmetic_expression) => {
1096                SymbolSubstitution::Hit(span, script, arithmetic_expression)
1097            }
1098            SymbolSubstitution::Omit => SymbolSubstitution::Omit,
1099            SymbolSubstitution::Zero(span) => SymbolSubstitution::Zero(span),
1100        }
1101    }
1102}
1103
1104/// Part of a TX-2 instruction.
1105///
1106/// In the M4 assembly language, each part is evaluated separately and
1107/// then they are combined.
1108#[derive(Debug, Clone, PartialEq, Eq)]
1109pub(crate) enum InstructionFragment {
1110    /// Arithmetic expressions are permitted in normal case according
1111    /// to the Users Handbook, but currently this implementation
1112    /// allows them in subscript/superscript too.
1113    Arithmetic(ArithmeticExpression),
1114    /// Insicates that the current instruction should use the deferred
1115    /// addressing mode.
1116    ///
1117    /// The programmer indicates this with `*`.  The
1118    /// programmer can also make use of deferred addressing by using
1119    /// the "pipe construct" (which is represented by
1120    /// [`InstructionFragment::PipeConstruct`])
1121    DeferredAddressing(Span),
1122    /// A configuration syllable (specified either in superscript or with a ‖).
1123    Config(ConfigValue),
1124    /// Described in section 6-2.8 "SPECIAL SYMBOLS" of the Users Handbook.
1125    PipeConstruct {
1126        index: SpannedSymbolOrLiteral,
1127        rc_word_span: Span,
1128        rc_word_value: RegisterContaining,
1129    },
1130    /// Purely an implementation artifact.
1131    ///
1132    /// Used by the assembler when parsing a
1133    /// [`CommaDelimitedFragment`] to represent the commas at the
1134    /// beginning or end of a part of the input program which gets
1135    /// parsed as an [`UntaggedProgramInstruction`].
1136    Null(Span),
1137}
1138
1139impl Spanned for InstructionFragment {
1140    fn span(&self) -> Span {
1141        match self {
1142            InstructionFragment::Arithmetic(arithmetic_expression) => arithmetic_expression.span(),
1143            InstructionFragment::Config(config_value) => config_value.span(),
1144            InstructionFragment::PipeConstruct {
1145                index,
1146                rc_word_span,
1147                rc_word_value: _,
1148            } => {
1149                let start = index.span.start;
1150                let end = rc_word_span.end;
1151                span(start..end)
1152            }
1153            InstructionFragment::DeferredAddressing(span) | InstructionFragment::Null(span) => {
1154                *span
1155            }
1156        }
1157    }
1158}
1159
1160impl InstructionFragment {
1161    fn symbol_uses(
1162        &self,
1163        block_id: BlockIdentifier,
1164        block_offset: Unsigned18Bit,
1165    ) -> impl Iterator<Item = Result<(SymbolName, Span, SymbolUse), InconsistentSymbolUse>> + use<>
1166    {
1167        let mut uses: Vec<Result<(SymbolName, Span, SymbolUse), InconsistentSymbolUse>> =
1168            Vec::new();
1169        match self {
1170            InstructionFragment::Arithmetic(expr) => {
1171                uses.extend(expr.symbol_uses(block_id, block_offset));
1172            }
1173            InstructionFragment::Null(_) | InstructionFragment::DeferredAddressing(_) => (),
1174            InstructionFragment::Config(value) => {
1175                uses.extend(value.symbol_uses(block_id, block_offset));
1176            }
1177            InstructionFragment::PipeConstruct {
1178                index,
1179                rc_word_span: _,
1180                rc_word_value,
1181            } => {
1182                for r in index.item.symbol_uses() {
1183                    match r {
1184                        Ok((name, span, mut symbol_use)) => {
1185                            if let SymbolUse::Reference(context) = &mut symbol_use {
1186                                assert!(!context.is_address());
1187                                if let Err(e) = context.also_set_index(&name, span) {
1188                                    uses.push(Err(e));
1189                                } else {
1190                                    uses.push(Ok((name, span, symbol_use)));
1191                                }
1192                            } else {
1193                                uses.push(Ok((name, span, symbol_use)));
1194                            }
1195                        }
1196                        Err(e) => {
1197                            uses.push(Err(e));
1198                        }
1199                    }
1200                }
1201                uses.extend(rc_word_value.symbol_uses(block_id, block_offset));
1202            }
1203        }
1204        uses.into_iter()
1205    }
1206
1207    fn substitute_macro_parameters(
1208        &self,
1209        param_values: &MacroParameterBindings,
1210        on_missing: OnUnboundMacroParameter,
1211        macros: &BTreeMap<SymbolName, MacroDefinition>,
1212    ) -> Option<InstructionFragment> {
1213        match self {
1214            InstructionFragment::Arithmetic(arithmetic_expression) => arithmetic_expression
1215                .substitute_macro_parameters(param_values, on_missing, macros)
1216                .map(InstructionFragment::Arithmetic),
1217            InstructionFragment::DeferredAddressing(span) => {
1218                Some(InstructionFragment::DeferredAddressing(*span))
1219            }
1220            InstructionFragment::Config(config_value) => config_value
1221                .substitute_macro_parameters(param_values, on_missing, macros)
1222                .map(InstructionFragment::Config),
1223            InstructionFragment::PipeConstruct {
1224                index,
1225                rc_word_span,
1226                rc_word_value,
1227            } => match index.substitute_macro_parameters(param_values, on_missing) {
1228                SymbolSubstitution::AsIs(index) => rc_word_value
1229                    .substitute_macro_parameters(param_values, on_missing, macros)
1230                    .map(|rc_word_value| InstructionFragment::PipeConstruct {
1231                        index,
1232                        rc_word_span: *rc_word_span,
1233                        rc_word_value,
1234                    }),
1235                SymbolSubstitution::Hit(_span, _script, _arithmetic_expression) => {
1236                    todo!(
1237                        "macro parameter expansion is not yet fully supported in the index part of pipe constructs"
1238                    )
1239                }
1240                SymbolSubstitution::Omit => None,
1241                SymbolSubstitution::Zero(span) => Some(InstructionFragment::Null(span)),
1242            },
1243            InstructionFragment::Null(span) => Some(InstructionFragment::Null(*span)),
1244        }
1245    }
1246
1247    fn allocate_rc_words<R: RcAllocator>(
1248        &mut self,
1249        explicit_symtab: &mut ExplicitSymbolTable,
1250        implicit_symtab: &mut ImplicitSymbolTable,
1251        rc_allocator: &mut R,
1252    ) -> Result<(), RcWordAllocationFailure> {
1253        match self {
1254            InstructionFragment::Null(_) | InstructionFragment::DeferredAddressing(_) => Ok(()),
1255            InstructionFragment::Arithmetic(expr) => {
1256                expr.allocate_rc_words(explicit_symtab, implicit_symtab, rc_allocator)
1257            }
1258            InstructionFragment::Config(cfg) => {
1259                cfg.allocate_rc_words(explicit_symtab, implicit_symtab, rc_allocator)
1260            }
1261            InstructionFragment::PipeConstruct {
1262                index: _,
1263                rc_word_span,
1264                rc_word_value,
1265            } => {
1266                let span: Span = *rc_word_span;
1267                let w = rc_word_value.clone();
1268                *rc_word_value = w.assign_rc_word(
1269                    RcWordSource {
1270                        span,
1271                        kind: RcWordKind::PipeConstruct,
1272                    },
1273                    explicit_symtab,
1274                    implicit_symtab,
1275                    rc_allocator,
1276                )?;
1277                Ok(())
1278            }
1279        }
1280    }
1281}
1282
1283impl From<(Span, Script, Unsigned36Bit)> for InstructionFragment {
1284    fn from((span, script, v): (Span, Script, Unsigned36Bit)) -> InstructionFragment {
1285        // TODO: use the atomic variant instead.
1286        InstructionFragment::Arithmetic(ArithmeticExpression::from(Atom::from((span, script, v))))
1287    }
1288}
1289
1290impl From<ArithmeticExpression> for InstructionFragment {
1291    fn from(expr: ArithmeticExpression) -> Self {
1292        InstructionFragment::Arithmetic(expr)
1293    }
1294}
1295
1296/// Represents the origin of a block of program code.
1297#[derive(Debug, Clone, Eq, PartialEq, Hash)]
1298pub(crate) enum Origin {
1299    /// An origin specified directly as a number.
1300    Literal(Span, Address),
1301    /// An origin specified by name (which would refer to e.g. an
1302    /// equality).
1303    Symbolic(Span, SymbolName),
1304    /// A symbolic origin where the symbol had no definition and
1305    /// therefore the origin value had to be deduced (hence providing
1306    /// an implicit definition for the symbol).
1307    Deduced(Span, SymbolName, Address),
1308}
1309
1310impl Display for Origin {
1311    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
1312        match self {
1313            Origin::Literal(_span, addr) => fmt::Display::fmt(&addr, f),
1314            Origin::Symbolic(_span, sym) => fmt::Display::fmt(&sym, f),
1315            Origin::Deduced(_span, name, addr) => {
1316                write!(f, "{name} (deduced to be at address {addr:o})")
1317            }
1318        }
1319    }
1320}
1321
1322impl Origin {
1323    pub(super) fn default_address() -> Address {
1324        // Section 6-2.5 of the User Manual states that if the
1325        // manuscript contains no origin specification (no vertical
1326        // bar) the whole program is located (correctly) at 200_000
1327        // octal.
1328        Address::new(u18!(0o200_000))
1329    }
1330
1331    pub(super) fn symbol_uses(
1332        &self,
1333        block_id: BlockIdentifier,
1334    ) -> impl Iterator<Item = Result<(SymbolName, Span, SymbolUse), InconsistentSymbolUse>> + use<>
1335    {
1336        let mut result = Vec::with_capacity(1);
1337        match self {
1338            Origin::Literal(_span, _) => (),
1339            org @ Origin::Deduced(span, name, _) => {
1340                // We won't have any deduced origin values at this
1341                // time the symbol uses are enumerate, but this case
1342                // is just here for completeness.
1343                event!(
1344                    Level::WARN,
1345                    "unexpectedly saw a deduced value for origin {name} in symbol_uses"
1346                );
1347                result.push(Ok((
1348                    name.clone(),
1349                    *span,
1350                    SymbolUse::Definition(ExplicitDefinition::Origin(org.clone(), block_id)),
1351                )));
1352            }
1353            org @ Origin::Symbolic(span, name) => {
1354                result.push(Ok((
1355                    name.clone(),
1356                    *span,
1357                    SymbolUse::Definition(ExplicitDefinition::Origin(org.clone(), block_id)),
1358                )));
1359            }
1360        }
1361        result.into_iter()
1362    }
1363}
1364
1365impl Spanned for Origin {
1366    fn span(&self) -> Span {
1367        match self {
1368            Origin::Deduced(span, _, _) | Origin::Literal(span, _) | Origin::Symbolic(span, _) => {
1369                *span
1370            }
1371        }
1372    }
1373}
1374
1375impl Octal for Origin {
1376    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1377        match self {
1378            Origin::Deduced(_span, name, address) => {
1379                write!(f, "{name} (deduced to be at {address:o})")
1380            }
1381            Origin::Literal(_span, address) => fmt::Octal::fmt(&address, f),
1382            Origin::Symbolic(_span, name) => fmt::Display::fmt(&name, f),
1383        }
1384    }
1385}
1386
1387/// Indicates whether the hold bit is set, cleared or unspecified.
1388///
1389/// The hold bit is bit 4.9 of the instruction word (see section 6-2
1390/// of the Users Handbook).
1391///
1392/// The effect of the hold bit is explained in section 4-3.2 of the
1393/// Users Handbook.
1394#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1395pub(crate) enum HoldBit {
1396    /// The value of the hold bit was not specified.
1397    Unspecified,
1398    /// The hold bit should be set.
1399    Hold,
1400    /// The hold bit should be cleared.  The user might want to do
1401    /// this because some opcodes (`LDE`, `ITE`, `JPX`, `JNX`) would
1402    /// otherwise automatically set it.
1403    NotHold,
1404}
1405
1406/// One, two or three commas.
1407#[derive(Debug, Clone, PartialEq, Eq)]
1408pub(crate) enum Commas {
1409    One(Span),
1410    Two(Span),
1411    Three(Span),
1412}
1413
1414impl Spanned for Commas {
1415    fn span(&self) -> Span {
1416        match &self {
1417            Commas::One(span) | Commas::Two(span) | Commas::Three(span) => *span,
1418        }
1419    }
1420}
1421
1422/// Part of a TX-2 instruction, convertible to a 36-bit value.
1423#[derive(Debug, Clone, PartialEq, Eq)]
1424pub(super) struct FragmentWithHold {
1425    /// Location within the source code
1426    pub(super) span: Span,
1427    /// Indicates that this fragment sets the hold bit.
1428    pub(super) holdbit: HoldBit,
1429    /// The remaining value of the instruction fragment (without the
1430    /// possible hold bit).
1431    pub(super) fragment: InstructionFragment,
1432}
1433
1434/// An instruction fragment ([`FragmentWithHold`]) or one or more
1435/// commas.
1436#[derive(Debug, Clone, PartialEq, Eq)]
1437pub(crate) enum CommasOrInstruction {
1438    I(FragmentWithHold),
1439    C(Option<Commas>),
1440}
1441
1442/// A component (usually a number) of an assembly-language instruction
1443/// with possible leading or trailing commas.
1444///
1445/// These are described in section 6-2.4 of the Users Handbook.
1446#[derive(Debug, Clone, PartialEq, Eq)]
1447pub(super) struct CommaDelimitedFragment {
1448    /// Indicates where in the input we found it.
1449    pub(super) span: Span,
1450    /// Indicates how many commas preceded it (if any).
1451    pub(super) leading_commas: Option<Commas>,
1452    /// Indicates whether the item inside the commas includes a hold
1453    /// bit.
1454    pub(super) holdbit: HoldBit,
1455    /// The quantity itself, within the commas
1456    pub(super) fragment: InstructionFragment,
1457    /// Indicates how many commas followed it (if any).
1458    pub(super) trailing_commas: Option<Commas>,
1459}
1460
1461impl CommaDelimitedFragment {
1462    pub(super) fn new(
1463        leading_commas: Option<Commas>,
1464        instruction: FragmentWithHold,
1465        trailing_commas: Option<Commas>,
1466    ) -> Self {
1467        let span: Span = {
1468            let spans: [Option<Span>; 3] = [
1469                leading_commas.as_ref().map(Spanned::span),
1470                Some(instruction.span),
1471                trailing_commas.as_ref().map(Spanned::span),
1472            ];
1473            match spans {
1474                [_, None, _] => {
1475                    unreachable!("CommaDelimitedInstruction cannot be completely empty")
1476                }
1477                [None, Some(m), None] => m,
1478                [None, Some(m), Some(r)] => span(m.start..r.end),
1479                [Some(l), _, Some(r)] => span(l.start..r.end),
1480                [Some(l), Some(m), None] => span(l.start..m.end),
1481            }
1482        };
1483        Self {
1484            span,
1485            leading_commas,
1486            holdbit: instruction.holdbit,
1487            fragment: instruction.fragment,
1488            trailing_commas,
1489        }
1490    }
1491
1492    fn symbol_uses(
1493        &self,
1494        block_id: BlockIdentifier,
1495        block_offset: Unsigned18Bit,
1496    ) -> impl Iterator<Item = Result<(SymbolName, Span, SymbolUse), InconsistentSymbolUse>> + '_
1497    {
1498        self.fragment.symbol_uses(block_id, block_offset)
1499    }
1500
1501    fn substitute_macro_parameters(
1502        &self,
1503        param_values: &MacroParameterBindings,
1504        on_missing: OnUnboundMacroParameter,
1505        macros: &BTreeMap<SymbolName, MacroDefinition>,
1506    ) -> Option<CommaDelimitedFragment> {
1507        self.fragment
1508            .substitute_macro_parameters(param_values, on_missing, macros)
1509            .map(|fragment| Self {
1510                fragment,
1511                ..self.clone()
1512            })
1513    }
1514
1515    fn allocate_rc_words<R: RcAllocator>(
1516        &mut self,
1517        explicit_symtab: &mut ExplicitSymbolTable,
1518        implicit_symtab: &mut ImplicitSymbolTable,
1519        rc_allocator: &mut R,
1520    ) -> Result<(), RcWordAllocationFailure> {
1521        self.fragment
1522            .allocate_rc_words(explicit_symtab, implicit_symtab, rc_allocator)
1523    }
1524}
1525
1526impl Spanned for CommaDelimitedFragment {
1527    fn span(&self) -> Span {
1528        self.span
1529    }
1530}
1531
1532/// An instruction word in a TX-2 program.
1533#[derive(Debug, Clone, PartialEq, Eq)]
1534pub(crate) struct UntaggedProgramInstruction {
1535    pub(crate) fragments: OneOrMore<CommaDelimitedFragment>,
1536}
1537
1538impl From<OneOrMore<CommaDelimitedFragment>> for UntaggedProgramInstruction {
1539    fn from(fragments: OneOrMore<CommaDelimitedFragment>) -> Self {
1540        Self { fragments }
1541    }
1542}
1543
1544impl UntaggedProgramInstruction {
1545    fn symbol_uses(
1546        &self,
1547        block_id: BlockIdentifier,
1548        offset: Unsigned18Bit,
1549    ) -> impl Iterator<Item = Result<(SymbolName, Span, SymbolUse), InconsistentSymbolUse>> + use<'_>
1550    {
1551        self.fragments
1552            .iter()
1553            .flat_map(move |fragment| fragment.symbol_uses(block_id, offset))
1554    }
1555
1556    fn allocate_rc_words<R: RcAllocator>(
1557        &mut self,
1558        explicit_symtab: &mut ExplicitSymbolTable,
1559        implicit_symtab: &mut ImplicitSymbolTable,
1560        rc_allocator: &mut R,
1561    ) -> Result<(), RcWordAllocationFailure> {
1562        for inst in self.fragments.iter_mut() {
1563            inst.allocate_rc_words(explicit_symtab, implicit_symtab, rc_allocator)?;
1564        }
1565        Ok(())
1566    }
1567
1568    fn substitute_macro_parameters(
1569        &self,
1570        param_values: &MacroParameterBindings,
1571        on_missing: OnUnboundMacroParameter,
1572        macros: &BTreeMap<SymbolName, MacroDefinition>,
1573    ) -> Option<UntaggedProgramInstruction> {
1574        let tmp_frags: OneOrMore<Option<CommaDelimitedFragment>> = self
1575            .fragments
1576            .map(|frag| frag.substitute_macro_parameters(param_values, on_missing, macros));
1577        if tmp_frags.iter().any(Option::is_none) {
1578            None
1579        } else {
1580            Some(UntaggedProgramInstruction {
1581                fragments: tmp_frags.into_map(|mut maybe_frag| {
1582                    maybe_frag
1583                        .take()
1584                        .expect("we already checked this fragment wasn't None")
1585                }),
1586            })
1587        }
1588    }
1589}
1590
1591impl Spanned for UntaggedProgramInstruction {
1592    fn span(&self) -> Span {
1593        span(self.fragments.first().span.start..self.fragments.last().span.end)
1594    }
1595}
1596
1597/// The right-hand-side of an [`Equality`].
1598///
1599/// In other words, the value being assigned.
1600///
1601/// Equalities are described in section 6-2.2 of the Users Handbook.
1602#[derive(Debug, Clone, PartialEq, Eq)]
1603pub(super) struct EqualityValue {
1604    // Implementation choices:
1605    //
1606    // The RHS of an equality can be "any 36-bit value" (see TX-2 Users
1607    // Handbook, section 6-2.2, page 156 = 6-6).  Therefore it needs to
1608    // be possible to specify the value of the hold bit (since that is
1609    // one of those 36 bits).
1610    //
1611    // That means that if the right-hand-side of the assignment is
1612    // symbolic, the user needs to be able to set the hold bit with "h",
1613    // and we need to use in the representation something that records
1614    // that the hold bit is set.
1615    //
1616    // Although a [`TaggedProgramInstruction`] meets this requirement,
1617    // we do not use that, since we don't allow tags on the RHS, the
1618    // value cannot be a [`TaggedProgramInstruction`].
1619    //
1620    /// Location in the source code.
1621    pub(super) span: Span,
1622    /// The value which is assigned.
1623    pub(super) inner: UntaggedProgramInstruction,
1624}
1625
1626impl Spanned for EqualityValue {
1627    fn span(&self) -> Span {
1628        self.span
1629    }
1630}
1631
1632impl From<(Span, UntaggedProgramInstruction)> for EqualityValue {
1633    fn from((span, inner): (Span, UntaggedProgramInstruction)) -> Self {
1634        Self { span, inner }
1635    }
1636}
1637
1638impl EqualityValue {
1639    pub(super) fn substitute_macro_parameters(
1640        &self,
1641        param_values: &MacroParameterBindings,
1642        macros: &BTreeMap<SymbolName, MacroDefinition>,
1643    ) -> EqualityValue {
1644        // If we set an equality to the value of an unspecified macro
1645        // parameter, then that equality is set to zero.  This is
1646        // required by item (7) of section 6-4.6 ("The Defining
1647        // Subprogram") of the TX-2 User's Handbook.
1648        //
1649        // However, that item does not cover more complex cases like
1650        // "G = DUM2 + 4".  Our current interpretation will assign G
1651        // the value 4 when DUM2 is an unspecified macro parameter.
1652        // However, analysis of actual TX-2 programs may show that
1653        // this is not the correct interpretation.
1654        if let Some(inner) = self.inner.substitute_macro_parameters(
1655            param_values,
1656            // We use SubstituteZero here for the reasons
1657            // described in the block comment above.
1658            OnUnboundMacroParameter::SubstituteZero,
1659            macros,
1660        ) {
1661            EqualityValue {
1662                span: self.span,
1663                inner,
1664            }
1665        } else {
1666            unreachable!(
1667                "substitute_macro_parameters should not return None when OnUnboundMacroParameter::SubstituteZero is in effect"
1668            )
1669        }
1670    }
1671}
1672
1673/// A "Tag" is a symex used as a name for a place in a program.  A tag
1674/// is always terminated by an arrow ("->") and [in the symbol table]
1675/// it set to the numerical location of the word that it tags. [from
1676/// section 6-2.2 of the User's Handbook].
1677#[derive(Debug, Clone, PartialEq, Eq)]
1678pub(crate) struct Tag {
1679    pub(crate) name: SymbolName,
1680    pub(crate) span: Span,
1681}
1682
1683impl Tag {
1684    fn symbol_uses(
1685        &self,
1686        block_id: BlockIdentifier,
1687        block_offset: Unsigned18Bit,
1688    ) -> impl Iterator<Item = Result<(SymbolName, Span, SymbolUse), InconsistentSymbolUse>> + use<>
1689    {
1690        [Ok((
1691            self.name.clone(),
1692            self.span,
1693            SymbolUse::Definition(ExplicitDefinition::Tag(TagDefinition::Unresolved {
1694                block_id,
1695                block_offset,
1696                span: self.span,
1697            })),
1698        ))]
1699        .into_iter()
1700    }
1701}
1702
1703impl PartialOrd for Tag {
1704    /// Ordering for tags ignores the span.
1705    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1706        Some(self.cmp(other))
1707    }
1708}
1709
1710impl Ord for Tag {
1711    /// Ordering for tags ignores the span.
1712    fn cmp(&self, other: &Self) -> Ordering {
1713        self.name.cmp(&other.name)
1714    }
1715}
1716
1717/// A TX-2 instruction with zero or more [`Tag`]s.
1718#[derive(Debug, Clone, PartialEq, Eq)]
1719pub(crate) struct TaggedProgramInstruction {
1720    /// Location in the program
1721    pub(crate) span: Span,
1722    /// Tags which point to this instruction.
1723    pub(crate) tags: Vec<Tag>,
1724    /// The instruction itself.
1725    pub(crate) instruction: UntaggedProgramInstruction,
1726}
1727
1728impl TaggedProgramInstruction {
1729    pub(crate) fn symbol_uses(
1730        &self,
1731        block_id: BlockIdentifier,
1732        offset: Unsigned18Bit,
1733    ) -> impl Iterator<Item = Result<(SymbolName, Span, SymbolUse), InconsistentSymbolUse>> + use<>
1734    {
1735        let mut result: Vec<Result<_, _>> = Vec::new();
1736        result.extend(
1737            self.tags
1738                .iter()
1739                .flat_map(|tag| tag.symbol_uses(block_id, offset)),
1740        );
1741        result.extend(self.instruction.symbol_uses(block_id, offset));
1742        result.into_iter()
1743    }
1744
1745    #[cfg(test)]
1746    pub(crate) fn single(
1747        tags: Vec<Tag>,
1748        holdbit: HoldBit,
1749        inst_span: Span,
1750        frag_span: Span,
1751        frag: InstructionFragment,
1752    ) -> TaggedProgramInstruction {
1753        TaggedProgramInstruction {
1754            tags,
1755            span: inst_span,
1756            instruction: UntaggedProgramInstruction::from(OneOrMore::new(CommaDelimitedFragment {
1757                span: frag_span,
1758                leading_commas: None,
1759                holdbit,
1760                fragment: frag,
1761                trailing_commas: None,
1762            })),
1763        }
1764    }
1765
1766    #[cfg(test)]
1767    pub(crate) fn multiple(
1768        tags: Vec<Tag>,
1769        span: Span,
1770        first_fragment: CommaDelimitedFragment,
1771        more_fragments: Vec<CommaDelimitedFragment>,
1772    ) -> TaggedProgramInstruction {
1773        TaggedProgramInstruction {
1774            tags,
1775            span,
1776            instruction: UntaggedProgramInstruction::from(OneOrMore::with_tail(
1777                first_fragment,
1778                more_fragments,
1779            )),
1780        }
1781    }
1782
1783    fn emitted_word_count(&self) -> Unsigned18Bit {
1784        let _ = self;
1785        Unsigned18Bit::ONE
1786    }
1787
1788    pub(super) fn substitute_macro_parameters(
1789        &self,
1790        param_values: &MacroParameterBindings,
1791        on_missing: OnUnboundMacroParameter,
1792        macros: &BTreeMap<SymbolName, MacroDefinition>,
1793    ) -> Option<TaggedProgramInstruction> {
1794        self.instruction
1795            .substitute_macro_parameters(param_values, on_missing, macros)
1796            .map(|instruction| TaggedProgramInstruction {
1797                span: self.span,
1798                tags: self.tags.clone(),
1799                instruction,
1800            })
1801    }
1802
1803    fn allocate_rc_words<R: RcAllocator>(
1804        &mut self,
1805        explicit_symtab: &mut ExplicitSymbolTable,
1806        implicit_symtab: &mut ImplicitSymbolTable,
1807        rc_allocator: &mut R,
1808    ) -> Result<(), RcWordAllocationFailure> {
1809        self.instruction
1810            .allocate_rc_words(explicit_symtab, implicit_symtab, rc_allocator)
1811    }
1812}
1813
1814impl Spanned for TaggedProgramInstruction {
1815    fn span(&self) -> Span {
1816        let begin = match self.tags.first() {
1817            Some(t) => t.span.start,
1818            None => self.instruction.span().start,
1819        };
1820        let end = self.instruction.span().end;
1821        Span::from(begin..end)
1822    }
1823}
1824
1825#[derive(Debug, Clone, PartialEq, Eq)]
1826pub(crate) struct InstructionSequence {
1827    pub(super) local_symbols: Option<ExplicitSymbolTable>,
1828    pub(super) instructions: Vec<TaggedProgramInstruction>,
1829}
1830
1831#[cfg(test)]
1832impl From<Vec<TaggedProgramInstruction>> for InstructionSequence {
1833    fn from(v: Vec<TaggedProgramInstruction>) -> Self {
1834        InstructionSequence {
1835            local_symbols: None,
1836            instructions: v,
1837        }
1838    }
1839}
1840
1841impl FromIterator<TaggedProgramInstruction> for InstructionSequence {
1842    fn from_iter<T>(iter: T) -> Self
1843    where
1844        T: IntoIterator<Item = TaggedProgramInstruction>,
1845    {
1846        InstructionSequence {
1847            local_symbols: None,
1848            instructions: iter.into_iter().collect(),
1849        }
1850    }
1851}
1852
1853/// Enumerate a sequence of items, decorating each with an
1854/// `Unsigned18Bit` offset value.
1855pub(crate) fn block_items_with_offset<T, I>(items: I) -> impl Iterator<Item = (Unsigned18Bit, T)>
1856where
1857    I: Iterator<Item = T>,
1858{
1859    items.enumerate().map(|(offset, item)| {
1860        let off: Unsigned18Bit = Unsigned18Bit::try_from(offset)
1861            .expect("block should not be larger than the TX-2's memory");
1862        (off, item)
1863    })
1864}
1865
1866/// We failed to build a local symbol table.
1867#[derive(Debug, PartialEq, Eq, Clone)]
1868pub(crate) enum LocalSymbolTableBuildFailure {
1869    InconsistentUsage(InconsistentSymbolUse),
1870    BadDefinition(BadSymbolDefinition),
1871}
1872
1873impl Spanned for LocalSymbolTableBuildFailure {
1874    fn span(&self) -> Span {
1875        match self {
1876            LocalSymbolTableBuildFailure::InconsistentUsage(inconsistent_symbol_use) => {
1877                inconsistent_symbol_use.span()
1878            }
1879            LocalSymbolTableBuildFailure::BadDefinition(bad_symbol_definition) => {
1880                bad_symbol_definition.span()
1881            }
1882        }
1883    }
1884}
1885
1886impl Display for LocalSymbolTableBuildFailure {
1887    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1888        match self {
1889            LocalSymbolTableBuildFailure::InconsistentUsage(inconsistent_symbol_use) => {
1890                write!(f, "{inconsistent_symbol_use}")
1891            }
1892            LocalSymbolTableBuildFailure::BadDefinition(bad_symbol_definition) => {
1893                write!(f, "{bad_symbol_definition}")
1894            }
1895        }
1896    }
1897}
1898
1899impl Error for LocalSymbolTableBuildFailure {}
1900
1901impl InstructionSequence {
1902    pub(super) fn iter(&self) -> impl Iterator<Item = &TaggedProgramInstruction> {
1903        self.instructions.iter()
1904    }
1905
1906    pub(super) fn first(&self) -> Option<&TaggedProgramInstruction> {
1907        self.instructions.first()
1908    }
1909
1910    pub(crate) fn allocate_rc_words<R: RcAllocator>(
1911        &mut self,
1912        explicit_symtab: &mut ExplicitSymbolTable,
1913        implicit_symtab: &mut ImplicitSymbolTable,
1914        rc_allocator: &mut R,
1915    ) -> Result<(), RcWordAllocationFailure> {
1916        for ref mut statement in &mut self.instructions {
1917            statement.allocate_rc_words(explicit_symtab, implicit_symtab, rc_allocator)?;
1918        }
1919        Ok(())
1920    }
1921
1922    pub(crate) fn symbol_uses(
1923        &self,
1924        block_id: BlockIdentifier,
1925    ) -> impl Iterator<Item = Result<(SymbolName, Span, SymbolUse), InconsistentSymbolUse>> + use<>
1926    {
1927        let no_symbols = ExplicitSymbolTable::default();
1928        let local_scope: &ExplicitSymbolTable = self.local_symbols.as_ref().unwrap_or(&no_symbols);
1929        let mut result: Vec<Result<_, _>> = Vec::new();
1930
1931        for (off, statement) in block_items_with_offset(self.instructions.iter()) {
1932            result.extend(statement.symbol_uses(block_id, off).filter(|r| match r {
1933                Ok((symbol, _, _)) => !local_scope.is_defined(symbol),
1934                Err(_) => true,
1935            }));
1936        }
1937        result.into_iter()
1938    }
1939
1940    pub(crate) fn emitted_word_count(&self) -> Unsigned18Bit {
1941        self.iter()
1942            .map(TaggedProgramInstruction::emitted_word_count)
1943            .sum()
1944    }
1945
1946    #[allow(clippy::too_many_arguments)]
1947    pub(crate) fn build_binary_block<R: RcUpdater>(
1948        &self,
1949        location: Address,
1950        start_offset: Unsigned18Bit,
1951        explicit_symtab: &ExplicitSymbolTable,
1952        implicit_symtab: &mut ImplicitSymbolTable,
1953        memory_map: &MemoryMap,
1954        index_register_assigner: &mut IndexRegisterAssigner,
1955        rc_allocator: &mut R,
1956        final_symbols: &mut FinalSymbolTable,
1957        body: &Source<'_>,
1958        listing: &mut Listing,
1959        bad_symbol_definitions: &mut BTreeMap<SymbolName, ProgramError>,
1960    ) -> Result<Vec<Unsigned36Bit>, AssemblerFailure> {
1961        let mut words: Vec<Unsigned36Bit> = Vec::with_capacity(self.emitted_word_count().into());
1962        for (offset, instruction) in self.iter().enumerate() {
1963            let offset: Unsigned18Bit = Unsigned18Bit::try_from(offset)
1964                .ok()
1965                .and_then(|offset| offset.checked_add(start_offset))
1966                .expect("assembled code block should fit within physical memory");
1967            let address: Address = location.index_by(offset);
1968            for tag in &instruction.tags {
1969                final_symbols.define(
1970                    tag.name.clone(),
1971                    FinalSymbolType::Tag,
1972                    body.extract(tag.span.start..tag.span.end).to_string(),
1973                    FinalSymbolDefinition::PositionIndependent(address.into()),
1974                );
1975            }
1976
1977            if self.local_symbols.is_some() {
1978                unimplemented!(
1979                    "InstructionSequence::build_binary_block: evaluation with local symbol tables is not yet implemented"
1980                );
1981            }
1982            let mut ctx = EvaluationContext {
1983                explicit_symtab,
1984                implicit_symtab,
1985                memory_map,
1986                here: HereValue::Address(address),
1987                index_register_assigner,
1988                rc_updater: rc_allocator,
1989                lookup_operation: Default::default(),
1990            };
1991            let scope = ScopeIdentifier::global();
1992            match instruction.evaluate(&mut ctx, scope) {
1993                Ok(word) => {
1994                    listing.push_line(ListingLine {
1995                        span: Some(instruction.span),
1996                        rc_source: None,
1997                        content: Some((address, word)),
1998                    });
1999                    words.push(word);
2000                }
2001                Err(e) => {
2002                    record_undefined_symbol_or_return_failure(body, e, bad_symbol_definitions)?;
2003                }
2004            }
2005        }
2006        Ok(words)
2007    }
2008}
2009
2010/// An "Equality" (modern term: assignment).
2011///
2012#[derive(Debug, Clone, PartialEq, Eq)]
2013pub(crate) struct Equality {
2014    pub(crate) span: Span,
2015    pub(crate) name: SymbolName,
2016    pub(crate) value: EqualityValue,
2017}
2018
2019impl Equality {
2020    pub(super) fn symbol_uses(
2021        &self,
2022    ) -> impl Iterator<Item = Result<(SymbolName, Span, SymbolUse), InconsistentSymbolUse>> + use<>
2023    {
2024        [Ok((
2025            self.name.clone(),
2026            self.span,
2027            SymbolUse::Definition(
2028                // TODO: the expression.clone() on the next line is expensive.
2029                ExplicitDefinition::Equality(self.value.clone()),
2030            ),
2031        ))]
2032        .into_iter()
2033    }
2034}