1use 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 }
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 NotOrigin { config: ConfigUse, index: IndexUse },
179 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>, }
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 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 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 false
371 } else {
372 true
377 }
378 } else {
379 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 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 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 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 assert_eq!(fwd, rev);
427}
428
429#[test]
430fn test_origin_cannot_be_used_as_an_index_value() {
431 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 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 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 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 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 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 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 span(10..20)
543 } else {
544 span(2000..2020)
546 };
547
548 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 let reference_context = SymbolContext {
566 address: AddressUse::IncludesAddress,
567 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 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 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 let mut current = contexts_forward_ref.reference.clone(); 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}