assembler/
symbol.rs

1//! Symbol names and information about how these are used in the
2//! program.
3//!
4//! See [`super::symtab`] for the definitions of symbols.
5use std::collections::BTreeSet;
6use std::error::Error;
7use std::fmt::{self, Debug, Display, Formatter};
8use std::hash::{Hash, Hasher};
9
10use base::charset::Script;
11
12use super::ast::Origin;
13use super::span::Spanned;
14#[cfg(test)]
15use super::span::span;
16use super::span::{OrderableSpan, Span};
17use super::types::BlockIdentifier;
18
19#[derive(Clone, Eq, PartialOrd, Ord)]
20pub struct SymbolName {
21    pub(crate) canonical: String,
22    // pub(crate) as_used: String,
23}
24
25impl From<String> for SymbolName {
26    fn from(s: String) -> SymbolName {
27        SymbolName { canonical: s }
28    }
29}
30
31impl From<&str> for SymbolName {
32    fn from(s: &str) -> SymbolName {
33        SymbolName::from(s.to_string())
34    }
35}
36
37impl Display for SymbolName {
38    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
39        fmt::Display::fmt(&self.canonical, f)
40    }
41}
42
43impl Debug for SymbolName {
44    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
45        write!(f, "SymbolName {{ canonical: \"{}\" }}", self.canonical)
46    }
47}
48
49impl PartialEq for SymbolName {
50    fn eq(&self, other: &SymbolName) -> bool {
51        self.canonical.eq(&other.canonical)
52    }
53}
54
55impl Hash for SymbolName {
56    fn hash<H: Hasher>(&self, state: &mut H) {
57        self.canonical.hash(state);
58    }
59}
60
61#[derive(Debug, PartialEq, Eq, Clone, Copy)]
62pub(crate) enum ConfigOrIndexUsage {
63    Configuration,
64    Index,
65    ConfigurationAndIndex,
66}
67
68#[derive(Debug, PartialEq, Eq, Clone)]
69pub(crate) enum InconsistentSymbolUse {
70    ConflictingOrigin {
71        name: SymbolName,
72        first: (Span, BlockIdentifier),
73        second: (Span, BlockIdentifier),
74    },
75    MixingOrigin(SymbolName, Span, ConfigOrIndexUsage),
76}
77
78impl Spanned for InconsistentSymbolUse {
79    fn span(&self) -> Span {
80        match self {
81            InconsistentSymbolUse::ConflictingOrigin {
82                name: _,
83                first: _,
84                second: (origin2_span, _),
85            } => *origin2_span,
86            InconsistentSymbolUse::MixingOrigin(_, span, _) => *span,
87        }
88    }
89}
90
91impl Display for InconsistentSymbolUse {
92    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
93        match self {
94            InconsistentSymbolUse::ConflictingOrigin {
95                name,
96                first: (_, block_identifier_1),
97                second: (_, block_identifier_2),
98            } => {
99                write!(
100                    f,
101                    "symbol {name} cannot simultaneously be the origin for {block_identifier_1} and {block_identifier_2}; names must be unique"
102                )
103            }
104            InconsistentSymbolUse::MixingOrigin(name, _, incompatibility) => {
105                let what: &'static str = match incompatibility {
106                    ConfigOrIndexUsage::Configuration => {
107                        "a configuration value (though using it as an index value would also be incorrect)"
108                    }
109                    ConfigOrIndexUsage::Index => {
110                        "an index value (though using it as a configuration value would also be incorrect)"
111                    }
112                    ConfigOrIndexUsage::ConfigurationAndIndex => {
113                        "a configuration or index value (though in this case it was used as both)"
114                    }
115                };
116                write!(f, "symbols (in this case {name}) cannot be used as {what}")
117            }
118        }
119    }
120}
121
122impl Error for InconsistentSymbolUse {}
123
124#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
125pub(crate) enum ConfigUse {
126    #[default]
127    NotConfig,
128    IncludesConfig,
129}
130
131impl ConfigUse {
132    fn or(self, other: ConfigUse) -> ConfigUse {
133        match (self, other) {
134            (ConfigUse::IncludesConfig, _) | (_, ConfigUse::IncludesConfig) => {
135                ConfigUse::IncludesConfig
136            }
137            _ => ConfigUse::NotConfig,
138        }
139    }
140}
141
142#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
143pub(crate) enum IndexUse {
144    #[default]
145    NotIndex,
146    IncludesIndex,
147}
148
149impl IndexUse {
150    fn or(self, other: IndexUse) -> IndexUse {
151        match (self, other) {
152            (IndexUse::IncludesIndex, _) | (_, IndexUse::IncludesIndex) => IndexUse::IncludesIndex,
153            _ => IndexUse::NotIndex,
154        }
155    }
156}
157
158#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
159pub(crate) enum AddressUse {
160    #[default]
161    NotAddress,
162    IncludesAddress,
163}
164
165impl AddressUse {
166    fn or(self, other: AddressUse) -> AddressUse {
167        match (self, other) {
168            (AddressUse::NotAddress, AddressUse::NotAddress) => AddressUse::NotAddress,
169            _ => AddressUse::IncludesAddress,
170        }
171    }
172}
173
174#[derive(Debug, PartialEq, Eq, Clone)]
175pub(crate) enum OriginUse {
176    /// Use of a symbol in configuration or index context prohibits
177    /// use in an origin context.
178    NotOrigin { config: ConfigUse, index: IndexUse },
179    /// Use of a symbol in origin context prohibits use in
180    /// configuration or index context.
181    IncludesOrigin(BlockIdentifier, Origin),
182}
183
184#[derive(Debug, PartialEq, Eq, Clone)]
185pub(crate) struct SymbolContext {
186    pub(super) address: AddressUse,
187    pub(super) origin: OriginUse,
188    pub(super) uses: BTreeSet<OrderableSpan>, // Span does not implement Hash
189}
190
191impl SymbolContext {
192    fn check_invariants(&self) {
193        assert!(!self.uses.is_empty());
194    }
195
196    fn check_invariants_passthrough(self) -> SymbolContext {
197        self.check_invariants();
198        self
199    }
200
201    pub(crate) fn configuration(span: Span) -> SymbolContext {
202        SymbolContext {
203            address: AddressUse::NotAddress,
204            origin: OriginUse::NotOrigin {
205                config: ConfigUse::IncludesConfig,
206                index: IndexUse::NotIndex,
207            },
208            uses: SymbolContext::uses(span),
209        }
210        .check_invariants_passthrough()
211    }
212
213    pub(crate) fn also_set_index(
214        &mut self,
215        name: &SymbolName,
216        span: Span,
217    ) -> Result<(), InconsistentSymbolUse> {
218        match &mut self.origin {
219            OriginUse::IncludesOrigin(_block_identifier, origin) => {
220                Err(InconsistentSymbolUse::MixingOrigin(
221                    name.clone(),
222                    origin.span(),
223                    ConfigOrIndexUsage::Index,
224                ))
225            }
226            OriginUse::NotOrigin { index, .. } => {
227                *index = IndexUse::IncludesIndex;
228                self.uses.insert(OrderableSpan(span));
229                Ok(())
230            }
231        }
232    }
233
234    pub(crate) fn is_address(&self) -> bool {
235        self.address == AddressUse::IncludesAddress
236    }
237
238    pub(crate) fn get_origin(&self) -> Option<(BlockIdentifier, &Origin)> {
239        match self.origin {
240            OriginUse::IncludesOrigin(block_identifier, ref origin) => {
241                Some((block_identifier, origin))
242            }
243            OriginUse::NotOrigin { .. } => None,
244        }
245    }
246
247    fn uses(span: Span) -> BTreeSet<OrderableSpan> {
248        [OrderableSpan(span)].into_iter().collect()
249    }
250
251    pub(crate) fn origin(block_id: BlockIdentifier, origin: Origin) -> SymbolContext {
252        let span = origin.span();
253        SymbolContext {
254            // All origins are (implicitly) uses of the symbol as an
255            // address.
256            address: AddressUse::IncludesAddress,
257            origin: OriginUse::IncludesOrigin(block_id, origin),
258            uses: SymbolContext::uses(span),
259        }
260        .check_invariants_passthrough()
261    }
262
263    pub(crate) fn merge(
264        &mut self,
265        name: &SymbolName,
266        mut other: SymbolContext,
267    ) -> Result<(), InconsistentSymbolUse> {
268        fn mix_err(
269            name: &SymbolName,
270            origin: &Origin,
271            _block_id: BlockIdentifier,
272            configuration: ConfigUse,
273            index: IndexUse,
274        ) -> InconsistentSymbolUse {
275            let incompatiblity: ConfigOrIndexUsage = match (configuration, index) {
276                (ConfigUse::IncludesConfig, IndexUse::IncludesIndex) => {
277                    ConfigOrIndexUsage::ConfigurationAndIndex
278                }
279                (ConfigUse::IncludesConfig, IndexUse::NotIndex) => {
280                    ConfigOrIndexUsage::Configuration
281                }
282                (ConfigUse::NotConfig, IndexUse::IncludesIndex) => ConfigOrIndexUsage::Index,
283                (ConfigUse::NotConfig, IndexUse::NotIndex) => {
284                    unreachable!("enclosing match already eliminated this case")
285                }
286            };
287            InconsistentSymbolUse::MixingOrigin(name.clone(), origin.span(), incompatiblity)
288        }
289
290        let origin: OriginUse = match (&self.origin, &other.origin) {
291            (
292                OriginUse::NotOrigin {
293                    config: my_config_use,
294                    index: my_index_use,
295                },
296                OriginUse::NotOrigin {
297                    config: their_config_use,
298                    index: their_index_use,
299                },
300            ) => OriginUse::NotOrigin {
301                config: my_config_use.or(*their_config_use),
302                index: my_index_use.or(*their_index_use),
303            },
304            (
305                OriginUse::IncludesOrigin(my_block, my_origin),
306                OriginUse::IncludesOrigin(their_block, their_origin),
307            ) => {
308                if my_block == their_block {
309                    // If one of the origins is a deduced origin, that
310                    // has more information, so retain that one.
311                    //
312                    // We merge symbol usage information from deduced
313                    // origins because we specifically call
314                    // record_deduced_origin_value() in order to do
315                    // this.
316                    let chosen: &Origin = match (&my_origin, &their_origin) {
317                        (deduced @ Origin::Deduced(_, _, _), _)
318                        | (_, deduced @ Origin::Deduced(_, _, _)) => deduced,
319                        _ => my_origin,
320                    };
321                    OriginUse::IncludesOrigin(*my_block, chosen.clone())
322                } else {
323                    return Err(InconsistentSymbolUse::ConflictingOrigin {
324                        name: name.clone(),
325                        first: (my_origin.span(), *my_block),
326                        second: (their_origin.span(), *their_block),
327                    });
328                }
329            }
330            (
331                OriginUse::IncludesOrigin(my_block, my_origin),
332                OriginUse::NotOrigin { config, index },
333            ) => {
334                if config == &ConfigUse::IncludesConfig || index == &IndexUse::IncludesIndex {
335                    return Err(mix_err(name, my_origin, *my_block, *config, *index));
336                }
337                OriginUse::IncludesOrigin(*my_block, my_origin.clone())
338            }
339            (
340                OriginUse::NotOrigin { config, index },
341                OriginUse::IncludesOrigin(their_block, their_origin),
342            ) => {
343                if config == &ConfigUse::IncludesConfig || index == &IndexUse::IncludesIndex {
344                    return Err(mix_err(name, their_origin, *their_block, *config, *index));
345                }
346                OriginUse::IncludesOrigin(*their_block, their_origin.clone())
347            }
348        };
349        let result = SymbolContext {
350            address: self.address.or(other.address),
351            origin,
352            uses: {
353                let mut u = BTreeSet::new();
354                u.append(&mut self.uses);
355                u.append(&mut other.uses);
356                u
357            },
358        };
359        result.check_invariants();
360        *self = result;
361        Ok(())
362    }
363
364    pub(super) fn requires_rc_word_allocation(&self) -> bool {
365        if self.address == AddressUse::IncludesAddress {
366            if matches!(&self.origin, &OriginUse::IncludesOrigin(_, _)) {
367                // This symbol is used in address contexts, but it is
368                // the name used for an origin.  Therefore we do not
369                // need to allocate an RC-word for it.
370                false
371            } else {
372                // This name is used in an address context but it is
373                // not the name of an origin, so when there is no tag
374                // definition for it, we will need to allocate an
375                // RC-word for it.
376                true
377            }
378        } else {
379            // Since nothing expects this symbol to refer to an
380            // address, there is no need to allocate an RC-word.
381            false
382        }
383    }
384
385    pub(super) fn any_span(&self) -> &Span {
386        match self.uses.first() {
387            Some(orderable_span) => orderable_span.as_span(),
388            None => {
389                panic!(
390                    "invariant broken in SymbolContext::any_span(): SymbolContext contains empty uses"
391                );
392            }
393        }
394    }
395}
396
397#[test]
398fn test_origin_can_be_used_as_address() {
399    // Given a symbol usage as an origin name
400    let name = SymbolName::from("BEGIN");
401    let make_origin_context = || {
402        SymbolContext::origin(
403            BlockIdentifier::from(0),
404            Origin::Symbolic(span(10..15), name.clone()),
405        )
406    };
407    // And a symbol usage in an address context.
408    let make_address_context = || SymbolContext {
409        address: AddressUse::IncludesAddress,
410        origin: OriginUse::NotOrigin {
411            config: ConfigUse::NotConfig,
412            index: IndexUse::NotIndex,
413        },
414        uses: SymbolContext::uses(span(20..25)),
415    };
416
417    // When we merge these two uses of the same symbol, this should
418    // succeed.
419    let mut fwd = make_origin_context();
420    assert_eq!(fwd.merge(&name, make_address_context()), Ok(()));
421
422    let mut rev = make_address_context();
423    assert_eq!(rev.merge(&name, make_origin_context()), Ok(()));
424
425    // We should get the same result in either case.
426    assert_eq!(fwd, rev);
427}
428
429#[test]
430fn test_origin_cannot_be_used_as_an_index_value() {
431    // Given a symbol usage as an origin name
432    let name = SymbolName::from("BEGIN");
433    let make_origin_context = || {
434        SymbolContext::origin(
435            BlockIdentifier::from(0),
436            Origin::Symbolic(span(10..15), name.clone()),
437        )
438    };
439    // And a symbol usage in an index context.
440    let make_index_context = || SymbolContext {
441        address: AddressUse::NotAddress,
442        origin: OriginUse::NotOrigin {
443            config: ConfigUse::NotConfig,
444            index: IndexUse::IncludesIndex,
445        },
446        uses: SymbolContext::uses(span(20..25)),
447    };
448
449    // When we merge these two uses of the same symbol, this should
450    // fail (as this combination is not allowed)
451    let expected_msg = "symbols (in this case BEGIN) cannot be used as an index value (though using it as a configuration value would also be incorrect)";
452    let mut fwd = make_origin_context();
453    match fwd.merge(&name, make_index_context()) {
454        Ok(()) => {
455            panic!(
456                "failed to detect incompatibility in the use of a symbol as both origin and index"
457            );
458        }
459        Err(e) => {
460            assert_eq!(e.to_string(), expected_msg);
461        }
462    }
463
464    let mut rev = make_index_context();
465    match rev.merge(&name, make_origin_context()) {
466        Ok(()) => {
467            panic!(
468                "failed to detect incompatibility in the use of a symbol as both origin and index"
469            );
470        }
471        Err(e) => {
472            assert_eq!(e.to_string(), expected_msg);
473        }
474    }
475}
476
477#[test]
478fn test_origin_cannot_be_used_as_a_configuration_value() {
479    // Given a symbol usage as an origin name
480    let name = SymbolName::from("BEGIN");
481    let make_origin_context = || {
482        SymbolContext::origin(
483            BlockIdentifier::from(0),
484            Origin::Symbolic(span(10..15), name.clone()),
485        )
486    };
487    // And a symbol usage in a configuration context.
488    let make_configuration_context = || SymbolContext {
489        address: AddressUse::NotAddress,
490        origin: OriginUse::NotOrigin {
491            config: ConfigUse::IncludesConfig,
492            index: IndexUse::NotIndex,
493        },
494        uses: SymbolContext::uses(span(20..25)),
495    };
496
497    // When we merge these two uses of the same symbol, this should
498    // fail (as this combination is not allowed)
499    let expected_msg = "symbols (in this case BEGIN) cannot be used as a configuration value (though using it as an index value would also be incorrect)";
500    let mut fwd = make_origin_context();
501    match fwd.merge(&name, make_configuration_context()) {
502        Ok(()) => {
503            panic!(
504                "failed to detect incompatibility in the use of a symbol as both origin and index"
505            );
506        }
507        Err(e) => {
508            assert_eq!(e.to_string(), expected_msg);
509        }
510    }
511
512    let mut rev = make_configuration_context();
513    match rev.merge(&name, make_origin_context()) {
514        Ok(()) => {
515            panic!(
516                "failed to detect incompatibility in the use of a symbol as both origin and configuration value"
517            );
518        }
519        Err(e) => {
520            assert_eq!(e.to_string(), expected_msg);
521        }
522    }
523}
524
525#[test]
526fn test_deduced_origin_merge() {
527    struct Contexts {
528        reference: SymbolContext,
529        origin_definition: SymbolContext,
530        expected_merge: SymbolContext,
531    }
532    // Convenience function for creating the test input and expected output.
533    fn make_symbolic_and_deduced_origin_contexts(
534        name: &SymbolName,
535        is_forward_reference: bool,
536    ) -> Contexts {
537        let block = BlockIdentifier::from(0);
538
539        let deduced_origin_span: Span = span(1000..1010);
540        let symbol_span = if is_forward_reference {
541            // The reference appears before the origin specification
542            span(10..20)
543        } else {
544            // The reference appears after the origin specification
545            span(2000..2020)
546        };
547
548        // In our examples, the deduced origin value is a
549        // symbolically-defined origin to which we would deduced
550        // address (from the locations and sizes of the blocks
551        // preceding it).
552        let deduced_origin_context = SymbolContext {
553            address: AddressUse::IncludesAddress,
554            origin: OriginUse::IncludesOrigin(
555                block,
556                Origin::Symbolic(deduced_origin_span, name.clone()),
557            ),
558            uses: SymbolContext::uses(deduced_origin_span),
559        };
560
561        // The symbolic context is is simply a reference to the
562        // origin, either preceding (is_forward_reference) or
563        // following (!is_forward_reference) the definition of the
564        // origin.
565        let reference_context = SymbolContext {
566            address: AddressUse::IncludesAddress,
567            // Although this use of the symbol will turn out to be a
568            // reference to an origin, we cannot tell that at the
569            // reference site, and so the context in which this symbol
570            // is used at that point is not an origin context.
571            origin: OriginUse::NotOrigin {
572                config: ConfigUse::NotConfig,
573                index: IndexUse::NotIndex,
574            },
575            uses: SymbolContext::uses(symbol_span),
576        };
577
578        Contexts {
579            reference: reference_context,
580            origin_definition: deduced_origin_context,
581            expected_merge: SymbolContext {
582                address: AddressUse::IncludesAddress,
583                origin: OriginUse::IncludesOrigin(
584                    BlockIdentifier::from(0),
585                    Origin::Symbolic(deduced_origin_span, name.clone()),
586                ),
587                uses: [
588                    OrderableSpan(deduced_origin_span),
589                    OrderableSpan(symbol_span),
590                ]
591                .into_iter()
592                .collect(),
593            },
594        }
595    }
596    let name = SymbolName::from("OGNX");
597    // Set up contexts for the forward-reference and the backward-reference cases.
598    let contexts_backward_ref = make_symbolic_and_deduced_origin_contexts(&name, false);
599    let contexts_forward_ref = make_symbolic_and_deduced_origin_contexts(&name, true);
600    // The value in expected_merge is not the same in each case, since
601    // although the span of the origin definition is fixed, the span
602    // of the reference to it is different for the forward-reference
603    // and backward-reference cases.
604
605    // Merge for the defined-then-used direction (where we encouter
606    // the origin defintiion and later a reference to it).
607    let mut current = contexts_backward_ref.origin_definition.clone();
608    assert_eq!(
609        current.merge(&name, contexts_backward_ref.reference.clone()),
610        Ok(())
611    );
612    assert_eq!(current, contexts_backward_ref.expected_merge);
613
614    // Merge in forward-reference direction (where we find a reference
615    // to the origin address of a block before we have seen the origin
616    // definition of that block).
617    //
618    let mut current = contexts_forward_ref.reference.clone(); // we find the fwd ref first
619    assert_eq!(
620        current.merge(&name, contexts_forward_ref.origin_definition.clone()),
621        Ok(())
622    );
623    assert_eq!(current, contexts_forward_ref.expected_merge);
624}
625
626impl From<(&Script, Span)> for SymbolContext {
627    fn from((elevation, span): (&Script, Span)) -> SymbolContext {
628        let (config, index, address) = match elevation {
629            Script::Super => (
630                ConfigUse::IncludesConfig,
631                IndexUse::NotIndex,
632                AddressUse::NotAddress,
633            ),
634            Script::Sub => (
635                ConfigUse::NotConfig,
636                IndexUse::IncludesIndex,
637                AddressUse::NotAddress,
638            ),
639            Script::Normal => (
640                ConfigUse::NotConfig,
641                IndexUse::NotIndex,
642                AddressUse::IncludesAddress,
643            ),
644        };
645        SymbolContext {
646            address,
647            origin: OriginUse::NotOrigin { config, index },
648            uses: SymbolContext::uses(span),
649        }
650        .check_invariants_passthrough()
651    }
652}
653
654impl From<(Script, Span)> for SymbolContext {
655    fn from((elevation, span): (Script, Span)) -> SymbolContext {
656        SymbolContext::from((&elevation, span)).check_invariants_passthrough()
657    }
658}