1use std::collections::BTreeMap;
8
9use tracing::{Level, event};
10
11use base::Unsigned18Bit;
12#[cfg(test)]
13use base::Unsigned36Bit;
14use base::charset::Script;
15use base::prelude::Address;
16#[cfg(test)]
17use base::u18;
18
19use super::ast::ArithmeticExpression;
20use super::ast::Equality;
21use super::ast::EqualityValue;
22#[cfg(test)]
23use super::ast::HoldBit;
24#[cfg(test)]
25use super::ast::InstructionFragment;
26use super::ast::InstructionSequence;
27use super::ast::LocalSymbolTableBuildFailure;
28use super::ast::OnUnboundMacroParameter;
29use super::ast::Origin;
30use super::ast::SymbolUse;
31use super::ast::Tag;
32use super::ast::TaggedProgramInstruction;
33use super::ast::block_items_with_offset;
34use super::collections::OneOrMore;
35use super::directive::Directive;
36use super::lexer::Token;
37use super::memorymap::LocatedBlock;
38use super::memorymap::MemoryMap;
39use super::span::Span;
40use super::span::Spanned;
41#[cfg(test)]
42use super::span::span;
43use super::state::NumeralMode;
44use super::symbol::InconsistentSymbolUse;
45use super::symbol::SymbolContext;
46use super::symbol::SymbolName;
47#[cfg(test)]
48use super::symtab::BadSymbolDefinition;
49use super::symtab::ExplicitDefinition;
50use super::symtab::ExplicitSymbolTable;
51#[cfg(test)]
52use super::symtab::TagDefinition;
53use super::types::BlockIdentifier;
54
55fn offset_to_block_id<T>((offset, item): (usize, T)) -> (BlockIdentifier, T) {
56 (BlockIdentifier::from(offset), item)
57}
58
59fn definitions_only(
60 r: Result<(SymbolName, Span, SymbolUse), InconsistentSymbolUse>,
61) -> Option<Result<(SymbolName, Span, ExplicitDefinition), InconsistentSymbolUse>> {
62 match r {
63 Ok((
68 _,
69 _,
70 SymbolUse::Definition(ExplicitDefinition::Origin(_, _)) | SymbolUse::Reference(_),
71 )) => None,
72 Ok((name, span, SymbolUse::Definition(def))) => Some(Ok((name, span, def))),
73 Err(e) => Some(Err(e)),
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub(crate) struct SourceFile {
79 pub(crate) punch: Option<PunchCommand>,
80 pub(crate) blocks: Vec<ManuscriptBlock>,
84 pub(crate) global_equalities: Vec<Equality>,
85 pub(crate) macros: BTreeMap<SymbolName, MacroDefinition>,
86}
87
88impl SourceFile {
89 fn symbol_uses(
90 &self,
91 ) -> impl Iterator<Item = Result<(SymbolName, Span, SymbolUse), InconsistentSymbolUse>> + use<'_>
92 {
93 let uses_in_instructions = self
94 .blocks
95 .iter()
96 .enumerate()
97 .map(offset_to_block_id)
98 .flat_map(move |(block_id, block)| block.symbol_uses(block_id));
99 let uses_in_global_assignments = self
100 .global_equalities
101 .iter()
102 .flat_map(Equality::symbol_uses);
103 uses_in_instructions.chain(uses_in_global_assignments)
104 }
105
106 pub(crate) fn build_local_symbol_tables(
107 &mut self,
108 ) -> Result<(), OneOrMore<LocalSymbolTableBuildFailure>> {
109 let mut errors = Vec::default();
110 for (block_identifier, block) in self.blocks.iter_mut().enumerate().map(offset_to_block_id)
111 {
112 if let Err(e) = block.build_local_symbol_tables(block_identifier) {
113 errors.extend(e.into_iter());
114 }
115 }
116 match OneOrMore::try_from_vec(errors) {
117 Err(_) => Ok(()), Ok(errors) => Err(errors),
119 }
120 }
121
122 pub(crate) fn global_symbol_references(
123 &self,
124 ) -> impl Iterator<Item = Result<(SymbolName, Span, SymbolContext), InconsistentSymbolUse>> + '_
125 {
126 fn accept_references_only(
127 r: Result<(SymbolName, Span, SymbolUse), InconsistentSymbolUse>,
128 ) -> Option<Result<(SymbolName, Span, SymbolContext), InconsistentSymbolUse>> {
129 match r {
130 Ok((name, span, sym_use)) => match sym_use {
131 SymbolUse::Reference(context) => Some(Ok((name, span, context))),
132 SymbolUse::Definition(ExplicitDefinition::Origin(
134 ref origin @ Origin::Symbolic(span, ref name),
135 block_id,
136 )) => Some(Ok((
137 name.clone(),
138 span,
139 SymbolContext::origin(block_id, origin.clone()),
140 ))),
141 SymbolUse::Definition(_) => None,
142 },
143 Err(e) => Some(Err(e)),
144 }
145 }
146 self.symbol_uses().filter_map(accept_references_only)
147 }
148
149 pub(crate) fn global_symbol_definitions(
150 &self,
151 ) -> impl Iterator<Item = Result<(SymbolName, Span, ExplicitDefinition), InconsistentSymbolUse>> + '_
152 {
153 self.symbol_uses().filter_map(definitions_only)
154 }
155
156 pub(crate) fn into_directive(self, mem_map: &MemoryMap) -> Directive {
157 let SourceFile {
158 punch,
159 blocks: input_blocks,
160 global_equalities: equalities,
161 macros: _,
162 } = self;
163 let output_blocks: BTreeMap<BlockIdentifier, LocatedBlock> = input_blocks
164 .into_iter()
165 .enumerate()
166 .map(|(id, mblock)| (BlockIdentifier::from(id), mblock))
167 .map(|(block_id, mblock)| {
168 let location: Address = match mem_map.get(&block_id).map(|pos| pos.block_address) {
169 Some(Some(addr)) => addr,
170 None => unreachable!("unknown block {block_id} in input_blocks"),
171 Some(None) => unreachable!(
172 "block location for {block_id} should already have been determined"
173 ),
174 };
175 (
176 block_id,
177 LocatedBlock {
178 origin: mblock.origin,
179 location,
180 sequences: mblock.sequences,
181 },
182 )
183 })
184 .collect();
185
186 let entry_point: Option<Address> = match punch {
187 Some(PunchCommand(Some(address))) => {
188 event!(
189 Level::INFO,
190 "program entry point was specified as {address:o}"
191 );
192 Some(address)
193 }
194 Some(PunchCommand(None)) => {
195 event!(Level::INFO, "program entry point was not specified");
196 None
197 }
198 None => {
199 event!(
200 Level::WARN,
201 "No PUNCH directive was given, program has no start address"
202 );
203 None
204 }
205 };
206
207 Directive::new(output_blocks, equalities, entry_point)
220 }
221}
222
223fn build_local_symbol_table<'a, I>(
224 block_identifier: BlockIdentifier,
225 instructions: I,
226) -> Result<ExplicitSymbolTable, OneOrMore<LocalSymbolTableBuildFailure>>
227where
228 I: Iterator<Item = &'a TaggedProgramInstruction>,
229{
230 let mut errors: Vec<LocalSymbolTableBuildFailure> = Default::default();
231 let mut local_symbols = ExplicitSymbolTable::default();
232 for (offset, instruction) in block_items_with_offset(instructions) {
233 for r in instruction
234 .symbol_uses(block_identifier, offset)
235 .filter_map(definitions_only)
236 {
237 match r {
238 Ok((symbol_name, _span, definition)) => {
239 if let Err(e) = local_symbols.define(symbol_name.clone(), definition) {
240 errors.push(LocalSymbolTableBuildFailure::BadDefinition(e));
241 }
242 }
243 Err(e) => {
244 errors.push(LocalSymbolTableBuildFailure::InconsistentUsage(e));
245 }
246 }
247 }
248 }
249 match OneOrMore::try_from_vec(errors) {
250 Err(_) => Ok(local_symbols), Ok(errors) => Err(errors),
252 }
253}
254
255#[test]
256fn test_build_local_symbol_table_happy_case() {
257 let instruction_tagged_with = |name: &str, beginpos: usize| {
258 let tag_span = span(beginpos..(beginpos + 1));
259 let literal_span = span((beginpos + 3)..(beginpos + 4));
260 TaggedProgramInstruction::single(
261 vec![Tag {
262 name: SymbolName::from(name),
263 span: tag_span,
264 }],
265 HoldBit::Unspecified,
266 literal_span,
267 literal_span,
268 InstructionFragment::from((literal_span, Script::Normal, Unsigned36Bit::ZERO)),
269 )
270 };
271
272 let seq = InstructionSequence {
273 local_symbols: Some(ExplicitSymbolTable::default()),
274 instructions: vec![
275 instruction_tagged_with("T", 0),
278 instruction_tagged_with("U", 5),
279 ],
280 };
281
282 let mut expected: ExplicitSymbolTable = ExplicitSymbolTable::default();
283 expected
284 .define(
285 SymbolName::from("T"),
286 ExplicitDefinition::Tag(TagDefinition::Unresolved {
287 block_id: BlockIdentifier::from(0),
288 block_offset: u18!(0),
289 span: span(0..1),
290 }),
291 )
292 .expect("symbol definition should be OK since there is no other defintion for that symbol");
293 expected
294 .define(
295 SymbolName::from("U"),
296 ExplicitDefinition::Tag(TagDefinition::Unresolved {
297 block_id: BlockIdentifier::from(0),
298 block_offset: u18!(1),
299 span: span(5..6),
300 }),
301 )
302 .expect("symbol definition should be OK since there is no other defintion for that symbol");
303 assert_eq!(
304 build_local_symbol_table(BlockIdentifier::from(0), seq.iter()),
305 Ok(expected)
306 );
307}
308
309#[test]
310fn test_build_local_symbol_table_detects_tag_conflict() {
311 let instruction_tagged_with_t = |beginpos: usize| {
312 let tag_span = span(beginpos..(beginpos + 1));
315 let literal_span = span((beginpos + 3)..(beginpos + 4));
316 TaggedProgramInstruction::single(
317 vec![Tag {
318 name: SymbolName::from("T"),
319 span: tag_span,
320 }],
321 HoldBit::Unspecified,
322 literal_span,
323 literal_span,
324 InstructionFragment::from((literal_span, Script::Normal, Unsigned36Bit::ZERO)),
325 )
326 };
327
328 let seq = InstructionSequence {
329 local_symbols: Some(ExplicitSymbolTable::default()),
330 instructions: vec![
331 instruction_tagged_with_t(0),
334 instruction_tagged_with_t(5),
335 ],
336 };
337
338 assert_eq!(
339 build_local_symbol_table(BlockIdentifier::from(0), seq.iter()),
340 Err(OneOrMore::new(LocalSymbolTableBuildFailure::BadDefinition(
341 BadSymbolDefinition {
342 symbol_name: SymbolName::from("T"),
343 span: span(5..6),
344 existing: Box::new(ExplicitDefinition::Tag(TagDefinition::Unresolved {
345 block_id: BlockIdentifier::from(0),
346 block_offset: u18!(0),
347 span: span(0..1),
348 })),
349 proposed: Box::new(ExplicitDefinition::Tag(TagDefinition::Unresolved {
350 block_id: BlockIdentifier::from(0),
351 block_offset: u18!(1),
352 span: span(5..6),
353 }))
354 }
355 )))
356 );
357}
358
359#[derive(Debug, Clone, PartialEq, Eq)]
360pub(crate) struct ManuscriptBlock {
361 pub(crate) origin: Option<Origin>,
362 pub(crate) sequences: Vec<InstructionSequence>,
367}
368
369impl ManuscriptBlock {
370 pub(super) fn symbol_uses(
371 &self,
372 block_id: BlockIdentifier,
373 ) -> impl Iterator<Item = Result<(SymbolName, Span, SymbolUse), InconsistentSymbolUse>> + use<>
374 {
375 let mut result: Vec<Result<(SymbolName, Span, SymbolUse), InconsistentSymbolUse>> =
376 Vec::new();
377 if let Some(origin) = self.origin.as_ref() {
378 result.extend(origin.symbol_uses(block_id));
379 }
380 result.extend(
381 self.sequences
382 .iter()
383 .flat_map(|seq| seq.symbol_uses(block_id)),
384 );
385 result.into_iter()
386 }
387
388 fn build_local_symbol_tables(
389 &mut self,
390 block_identifier: BlockIdentifier,
391 ) -> Result<(), OneOrMore<LocalSymbolTableBuildFailure>> {
392 let mut errors: Vec<LocalSymbolTableBuildFailure> = Vec::new();
393 for seq in &mut self.sequences {
394 if let Some(local_symbols) = seq.local_symbols.as_mut() {
395 match build_local_symbol_table(block_identifier, seq.instructions.iter()) {
396 Ok(more_symbols) => match local_symbols.merge(more_symbols) {
397 Ok(()) => (),
398 Err(e) => {
399 errors.extend(
400 e.into_iter()
401 .map(LocalSymbolTableBuildFailure::BadDefinition),
402 );
403 }
404 },
405 Err(e) => {
406 errors.extend(e.into_iter());
407 }
408 }
409 }
410 }
411 match OneOrMore::try_from_vec(errors) {
412 Ok(errors) => Err(errors),
413 Err(_) => Ok(()),
414 }
415 }
416
417 pub(crate) fn instruction_count(&self) -> Unsigned18Bit {
418 self.sequences
419 .iter()
420 .map(InstructionSequence::emitted_word_count)
421 .sum()
422 }
423
424 pub(crate) fn origin_span(&self) -> Span {
425 if let Some(origin) = self.origin.as_ref() {
426 origin.span()
427 } else {
428 if let Some(s) = self.sequences.first()
429 && let Some(first) = s.first()
430 {
431 return first.span();
432 }
433 Span::from(0..0)
434 }
435 }
436
437 pub(super) fn push_unscoped_instruction(&mut self, inst: TaggedProgramInstruction) {
438 if let Some(InstructionSequence {
439 local_symbols: None,
440 instructions,
441 }) = self.sequences.last_mut()
442 {
443 instructions.push(inst);
444 } else {
445 self.sequences.push(InstructionSequence {
446 local_symbols: None,
447 instructions: vec![inst],
448 });
449 }
450 }
451}
452
453#[derive(Debug, Clone, Copy, PartialEq, Eq)]
455pub(crate) struct PunchCommand(pub(crate) Option<Address>);
456
457#[derive(Debug, Clone, PartialEq, Eq)]
458pub(crate) enum ManuscriptMetaCommand {
459 BaseChange(NumeralMode),
462
463 Punch(PunchCommand),
465 Macro(MacroDefinition),
466}
467
468#[derive(Debug, Clone, PartialEq, Eq)]
469pub(crate) enum ManuscriptLine {
470 Meta(ManuscriptMetaCommand),
471 Macro(MacroInvocation),
472 Eq(Equality),
473 OriginOnly(Origin),
474 TagsOnly(Vec<Tag>),
475 StatementOnly(TaggedProgramInstruction),
476 OriginAndStatement(Origin, TaggedProgramInstruction),
477}
478
479#[derive(Debug, Clone, PartialEq, Eq)]
480pub(crate) struct MacroParameter {
481 pub(crate) name: SymbolName,
482 pub(crate) span: Span,
483 pub(crate) preceding_terminator: Token,
486}
487
488#[derive(Debug, Clone, PartialEq, Eq)]
495pub(crate) enum MacroDummyParameters {
496 Zero(Token),
497 OneOrMore(Vec<MacroParameter>),
498}
499#[derive(Debug, Clone, PartialEq, Eq)]
500pub(crate) struct MacroDefinition {
501 pub(crate) name: SymbolName, pub(crate) params: MacroDummyParameters,
503 pub(crate) body: Vec<MacroBodyLine>,
504 pub(crate) span: Span,
505}
506
507#[derive(Debug, Clone, PartialEq, Eq)]
508pub(crate) enum MacroBodyLine {
509 Expansion(MacroInvocation),
510 Instruction(TaggedProgramInstruction),
511 Equality(Equality),
512}
513
514impl MacroDefinition {
515 fn substitute_macro_parameters(
516 &self,
517 bindings: &MacroParameterBindings,
518 macros: &BTreeMap<SymbolName, MacroDefinition>,
519 ) -> InstructionSequence {
520 let mut local_symbols = ExplicitSymbolTable::default();
521 let mut instructions: Vec<TaggedProgramInstruction> = Vec::with_capacity(self.body.len());
522 for body_line in &self.body {
523 match body_line {
524 MacroBodyLine::Expansion(_macro_invocation) => {
525 unimplemented!("recursive macros are not yet supported")
526 }
527 MacroBodyLine::Instruction(tagged_program_instruction) => {
528 if let Some(tagged_program_instruction) = tagged_program_instruction
529 .substitute_macro_parameters(
530 bindings,
531 OnUnboundMacroParameter::ElideReference,
532 macros,
533 )
534 {
535 instructions.push(tagged_program_instruction);
536 } else {
537 }
547 }
548 MacroBodyLine::Equality(Equality {
551 span: _,
552 name,
553 value,
554 }) => {
555 let value: EqualityValue = value.substitute_macro_parameters(bindings, macros);
556 if let Err(e) =
557 local_symbols.define(name.clone(), ExplicitDefinition::Equality(value))
558 {
559 panic!(
564 "unexpected failure when defining equality for {name} inside a macro body: {e}"
565 );
566 }
567 }
568 }
569 }
570 InstructionSequence {
571 local_symbols: Some(local_symbols),
575 instructions,
576 }
577 }
578}
579
580#[derive(Debug, Clone, PartialEq, Eq)]
581pub(crate) enum MacroParameterValue {
582 Value(Script, ArithmeticExpression),
583 }
586
587#[derive(Debug, Clone, PartialEq, Eq, Default)]
588pub(crate) struct MacroParameterBindings {
589 inner: BTreeMap<SymbolName, (Span, Option<MacroParameterValue>)>,
593}
594
595impl MacroParameterBindings {
596 pub(crate) fn insert(
597 &mut self,
598 name: SymbolName,
599 span: Span,
600 value: Option<MacroParameterValue>,
601 ) {
602 self.inner.insert(name, (span, value));
603 }
604
605 pub(super) fn get(&self, name: &SymbolName) -> Option<&(Span, Option<MacroParameterValue>)> {
606 self.inner.get(name)
607 }
608}
609
610#[derive(Debug, Clone, PartialEq, Eq)]
611pub(crate) struct MacroInvocation {
612 pub(crate) macro_def: MacroDefinition,
613 pub(crate) param_values: MacroParameterBindings,
614}
615
616impl MacroInvocation {
617 pub(super) fn substitute_macro_parameters(
618 &self,
619 macros: &BTreeMap<SymbolName, MacroDefinition>,
620 ) -> InstructionSequence {
621 self.macro_def
622 .substitute_macro_parameters(&self.param_values, macros)
623 }
624}
625
626fn expand_macro(
627 invocation: &MacroInvocation,
628 macros: &BTreeMap<SymbolName, MacroDefinition>,
629) -> InstructionSequence {
630 invocation.substitute_macro_parameters(macros)
631}
632
633pub(crate) fn manuscript_lines_to_source_file<'a>(
634 lines: Vec<(Span, ManuscriptLine)>,
635) -> Result<SourceFile, chumsky::error::Rich<'a, super::lexer::Token>> {
636 fn get_or_create_output_block(result: &mut Vec<ManuscriptBlock>) -> &mut ManuscriptBlock {
637 if result.is_empty() {
638 result.push(ManuscriptBlock {
639 origin: None,
640 sequences: Vec::new(),
641 });
642 }
643 result
644 .last_mut()
645 .expect("result cannot be empty because we just pushed an item onto it")
646 }
647
648 fn begin_new_block(origin: Origin, result: &mut Vec<ManuscriptBlock>) -> &mut ManuscriptBlock {
649 result.push(ManuscriptBlock {
650 origin: Some(origin),
651 sequences: Vec::new(),
652 });
653 result
654 .last_mut()
655 .expect("result cannot be empty because we just pushed an item onto it")
656 }
657
658 fn prepend_tags(v: &mut Vec<Tag>, initial: &mut Vec<Tag>) {
659 let alltags: Vec<Tag> = initial.drain(0..).chain(v.drain(0..)).collect();
660 v.extend(alltags);
661 }
662
663 let mut blocks: Vec<ManuscriptBlock> = Vec::new();
664 let mut equalities: Vec<Equality> = Vec::new();
665 let mut macros: BTreeMap<SymbolName, MacroDefinition> = Default::default();
666 let mut maybe_punch: Option<PunchCommand> = None;
667
668 let mut pending_tags: Vec<Tag> = Vec::new();
669
670 let bad_tag_pos = |tag: Tag| {
671 let tag_name = &tag.name;
672 chumsky::error::Rich::custom(
673 tag.span,
674 format!("tag {tag_name} must be followed by an instruction"),
675 )
676 };
677
678 for (_span, line) in lines {
679 match line {
680 ManuscriptLine::Macro(invocation) => {
681 get_or_create_output_block(&mut blocks)
682 .sequences
683 .push(expand_macro(&invocation, ¯os));
684 }
685 ManuscriptLine::TagsOnly(tags) => {
686 pending_tags.extend(tags);
687 }
688
689 ManuscriptLine::Meta(_)
690 | ManuscriptLine::OriginOnly(_)
691 | ManuscriptLine::OriginAndStatement(_, _)
692 | ManuscriptLine::Eq(_)
693 if !pending_tags.is_empty() =>
694 {
695 return Err(bad_tag_pos(
696 pending_tags
697 .pop()
698 .expect("we know pending_tags is non-empty"),
699 ));
700 }
701
702 ManuscriptLine::Meta(ManuscriptMetaCommand::Punch(punch)) => {
703 maybe_punch = Some(punch);
704 }
705 ManuscriptLine::Meta(ManuscriptMetaCommand::BaseChange(_)) => {
706 }
710 ManuscriptLine::Meta(ManuscriptMetaCommand::Macro(macro_def)) => {
711 if let Some(previous) = macros.insert(macro_def.name.clone(), macro_def) {
712 event!(Level::INFO, "Redefinition of macro {}", &previous.name);
713 }
714 }
715
716 ManuscriptLine::OriginOnly(origin) => {
717 begin_new_block(origin, &mut blocks);
718 }
719 ManuscriptLine::StatementOnly(mut tagged_program_instruction) => {
720 prepend_tags(&mut tagged_program_instruction.tags, &mut pending_tags);
721 get_or_create_output_block(&mut blocks)
722 .push_unscoped_instruction(tagged_program_instruction);
723 }
724 ManuscriptLine::OriginAndStatement(origin, statement) => {
725 begin_new_block(origin, &mut blocks).push_unscoped_instruction(statement);
726 }
727 ManuscriptLine::Eq(equality) => {
728 equalities.push(equality);
729 }
730 }
731 }
732 if let Some(t) = pending_tags.pop() {
733 return Err(bad_tag_pos(t));
734 }
735
736 Ok(SourceFile {
737 punch: maybe_punch,
738 blocks,
739 global_equalities: equalities,
740 macros,
741 })
742}