Skip to content

Commit 9b5977a

Browse files
committed
[assembler] Introduce local scopes. Diagnose inconsistent tags.
The AST can now represent local scopes, as would be produced by the expansion of a macro. But macro expansion is not yet implemented, so no actual examples of local scopes are created by the parsing process.
1 parent c91f23e commit 9b5977a

File tree

10 files changed

+624
-387
lines changed

10 files changed

+624
-387
lines changed

assembler/src/asmlib/ast.rs

Lines changed: 212 additions & 125 deletions
Large diffs are not rendered by default.

assembler/src/asmlib/collections.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,18 @@ impl<T> OneOrMore<T> {
8484
pub fn extend<I: Iterator<Item = T>>(&mut self, items: I) {
8585
self.tail.extend(items)
8686
}
87+
88+
pub fn try_from_vec(mut value: Vec<T>) -> Result<OneOrMore<T>, NoItems> {
89+
if value.is_empty() {
90+
Err(NoItems {})
91+
} else {
92+
let tail = value.split_off(1);
93+
Ok(OneOrMore::with_tail(
94+
value.pop().expect("known to be non-empty"),
95+
tail,
96+
))
97+
}
98+
}
8799
}
88100

89101
pub struct OneOrMoreIter<'a, T> {
@@ -166,7 +178,7 @@ impl<T: PartialEq<T>> PartialEq<Vec<T>> for OneOrMore<T> {
166178

167179
#[cfg(test)]
168180
mod one_or_more_tests {
169-
use super::OneOrMore;
181+
use super::{NoItems, OneOrMore};
170182

171183
#[test]
172184
fn test_first() {
@@ -285,4 +297,17 @@ mod one_or_more_tests {
285297
OneOrMore::with_tail(1, vec![2])
286298
);
287299
}
300+
301+
#[test]
302+
fn test_from_vec_to_option() {
303+
assert_eq!(Err(NoItems {}), OneOrMore::try_from_vec(Vec::<u64>::new()));
304+
assert_eq!(
305+
Ok(OneOrMore::new(2)),
306+
OneOrMore::try_from_vec(vec![2].into())
307+
);
308+
assert_eq!(
309+
Ok(OneOrMore::with_tail(10, vec![20])),
310+
OneOrMore::try_from_vec(vec![10, 20])
311+
);
312+
}
288313
}

assembler/src/asmlib/driver.rs

Lines changed: 149 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ use super::state::{NumeralMode, State};
2727
use super::symbol::SymbolName;
2828
use super::symtab::{
2929
assign_default_rc_word_tags, BadSymbolDefinition, BlockPosition, FinalSymbolDefinition,
30-
FinalSymbolTable, FinalSymbolType, IndexRegisterAssigner, MemoryMap, SymbolTable,
30+
FinalSymbolTable, FinalSymbolType, IndexRegisterAssigner, MemoryMap, SymbolDefinition,
31+
SymbolTable,
3132
};
3233
use super::types::*;
3334
use base::prelude::{Address, IndexBy, Unsigned18Bit, Unsigned36Bit};
@@ -67,7 +68,7 @@ impl SourceFile {
6768
let SourceFile {
6869
punch,
6970
blocks: input_blocks,
70-
equalities,
71+
global_equalities: equalities,
7172
macros: _,
7273
} = self;
7374
let output_blocks: BTreeMap<BlockIdentifier, LocatedBlock> = input_blocks
@@ -87,7 +88,7 @@ impl SourceFile {
8788
LocatedBlock {
8889
origin: mblock.origin,
8990
location,
90-
statements: mblock.statements,
91+
statements: mblock.sequences,
9192
},
9293
)
9394
})
@@ -147,48 +148,82 @@ fn assemble_pass1<'a>(
147148
state.numeral_mode.set_numeral_mode(NumeralMode::Octal);
148149
}
149150

150-
let (sf, mut new_errors) = parse_source_file(source_file_body, setup);
151+
let (mut sf, mut new_errors) = parse_source_file(source_file_body, setup);
151152
errors.append(&mut new_errors);
153+
154+
if let Some(source_file) = sf.as_mut() {
155+
if let Err(tag_errors) = source_file.build_local_symbol_tables() {
156+
errors.extend(
157+
tag_errors
158+
.into_iter()
159+
.map(|tag_err| Rich::custom(tag_err.span(), tag_err.to_string())),
160+
);
161+
}
162+
}
152163
Ok((sf, options))
153164
}
154165

166+
#[derive(Debug, PartialEq, Eq)]
167+
enum AssemblerPass1Or2Output<'a> {
168+
Pass1Failed(Result<Vec<Rich<'a, lexer::Token>>, AssemblerFailure>),
169+
Pass2Failed(AssemblerFailure),
170+
Success(Vec<Rich<'a, lexer::Token>>, OutputOptions, Pass2Output<'a>),
171+
}
172+
173+
fn assemble_nonempty_input(input: &str) -> AssemblerPass1Or2Output {
174+
let mut errors: Vec<Rich<'_, lexer::Token>> = Vec::new();
175+
match assemble_pass1(input, &mut errors) {
176+
Err(e) => AssemblerPass1Or2Output::Pass1Failed(Err(e)),
177+
Ok((None, _output_options)) => AssemblerPass1Or2Output::Pass1Failed(Ok(errors)),
178+
Ok((Some(source_file), output_options)) => match assemble_pass2(source_file, input) {
179+
Err(e) => AssemblerPass1Or2Output::Pass2Failed(e),
180+
Ok(p2output) => AssemblerPass1Or2Output::Success(errors, output_options, p2output),
181+
},
182+
}
183+
}
184+
155185
/// This test helper is defined here so that we don't have to expose
156186
/// assemble_pass1, assemble_pass2.
157187
#[cfg(test)]
158188
pub(crate) fn assemble_nonempty_valid_input(
159189
input: &str,
160190
) -> (Directive, SymbolTable, MemoryMap, IndexRegisterAssigner) {
161-
let mut errors: Vec<Rich<'_, lexer::Token>> = Vec::new();
162-
let result: Result<(Option<SourceFile>, OutputOptions), AssemblerFailure> =
163-
assemble_pass1(input, &mut errors);
164-
if !errors.is_empty() {
165-
panic!(
166-
"assemble_nonempty_valid_input: for input\n{input}\nerrors were reported: {errors:?}"
167-
);
168-
}
169-
match result {
170-
Ok((None, _)) => unreachable!("parser should generate output if there are no errors"),
171-
Ok((Some(source_file), _options)) => {
172-
let p2output = assemble_pass2(source_file, input)
173-
.expect("test program should not extend beyong physical memory");
174-
if !p2output.errors.is_empty() {
175-
panic!("input should be valid: {:?}", &p2output.errors);
176-
}
177-
match p2output.directive {
178-
Some(directive) => (
179-
directive,
180-
p2output.symbols,
181-
p2output.memory_map,
182-
p2output.index_register_assigner,
183-
),
184-
None => {
185-
panic!("assembly pass 2 generated no errors but also no output");
191+
match assemble_nonempty_input(input) {
192+
AssemblerPass1Or2Output::Pass1Failed(Err(e)) => {
193+
panic!("pass 1 failed with an error result: {e}");
194+
}
195+
AssemblerPass1Or2Output::Pass1Failed(Ok(errors)) => {
196+
panic!("pass 1 failed with diagnostics: {errors:?}");
197+
}
198+
AssemblerPass1Or2Output::Pass2Failed(e) => {
199+
panic!("pass 1 failed with an error result: {e}");
200+
}
201+
AssemblerPass1Or2Output::Success(errors, _output_options, p2output) => {
202+
if errors.is_empty() {
203+
match p2output {
204+
Pass2Output {
205+
directive: None, ..
206+
} => {
207+
panic!("directive is None but no errors were reported");
208+
}
209+
Pass2Output {
210+
directive: Some(directive),
211+
symbols,
212+
memory_map,
213+
index_register_assigner,
214+
errors,
215+
} => {
216+
if !errors.is_empty() {
217+
panic!("input should be valid: {:?}", &errors);
218+
} else {
219+
(directive, symbols, memory_map, index_register_assigner)
220+
}
221+
}
186222
}
223+
} else {
224+
panic!("pass 2 failed with diagnostics: {errors:?}");
187225
}
188226
}
189-
Err(e) => {
190-
panic!("input should be valid: {}", e);
191-
}
192227
}
193228
}
194229

@@ -257,11 +292,12 @@ impl Binary {
257292
}
258293
}
259294

295+
#[derive(Debug, PartialEq, Eq)]
260296
struct Pass2Output<'a> {
261297
directive: Option<Directive>,
262298
symbols: SymbolTable,
263299
memory_map: MemoryMap,
264-
index_register_assigner: IndexRegisterAssigner,
300+
index_register_assigner: IndexRegisterAssigner, // not cloneable
265301
errors: Vec<Rich<'a, lexer::Token>>,
266302
}
267303

@@ -270,11 +306,15 @@ fn initial_symbol_table<'a>(
270306
) -> Result<SymbolTable, Vec<Rich<'a, lexer::Token>>> {
271307
let mut errors = Vec::new();
272308
let mut symtab = SymbolTable::new();
273-
for (symbol, span, context) in source_file.global_symbol_references() {
274-
symtab.record_usage_context(symbol.clone(), span, context)
275-
}
309+
// All explicit definitions in the program take effect either
310+
// locally (for the bodies of macro expansions) or globally (for
311+
// everything else). So, before we can enumerate all global
312+
// symbol references, we need to identidy which symbol references
313+
// are references to something defined in a local scope. And so
314+
// we need to enumerate definitions before references.
276315
for (symbol, span, definition) in source_file.global_symbol_definitions() {
277-
match symtab.define(span, symbol.clone(), definition.clone()) {
316+
let definition = SymbolDefinition::Explicit(definition.clone());
317+
match symtab.define(span, symbol.clone(), definition) {
278318
Ok(_) => (),
279319
Err(e) => {
280320
errors.push(Rich::custom(
@@ -284,6 +324,9 @@ fn initial_symbol_table<'a>(
284324
}
285325
}
286326
}
327+
for (symbol, span, context) in source_file.global_symbol_references() {
328+
symtab.record_usage_context(symbol.clone(), span, context)
329+
}
287330
if errors.is_empty() {
288331
Ok(symtab)
289332
} else {
@@ -625,50 +668,51 @@ pub(crate) fn assemble_source(
625668
source_file_body: &str,
626669
mut options: OutputOptions,
627670
) -> Result<Binary, AssemblerFailure> {
628-
let mut errors: Vec<Rich<'_, lexer::Token>> = Vec::new();
629-
let (source_file, source_options) = assemble_pass1(source_file_body, &mut errors)?;
630-
if !errors.is_empty() {
631-
return Err(AssemblerFailure::BadProgram(fail_with_diagnostics(
632-
source_file_body,
633-
errors,
634-
)));
635-
}
636-
options = options.merge(source_options);
637-
638-
let source_file =
639-
source_file.expect("assembly pass1 generated no errors, an AST should have been returned");
640-
641-
// Now we do pass 2.
642-
let Pass2Output {
643-
directive,
644-
mut symbols,
645-
mut memory_map,
646-
mut index_register_assigner,
647-
errors,
648-
} = assemble_pass2(source_file, source_file_body)?;
649-
if !errors.is_empty() {
650-
return Err(AssemblerFailure::BadProgram(fail_with_diagnostics(
651-
source_file_body,
671+
let mut p2output = match assemble_nonempty_input(source_file_body) {
672+
AssemblerPass1Or2Output::Pass1Failed(Err(e)) => {
673+
return Err(e);
674+
}
675+
AssemblerPass1Or2Output::Pass1Failed(Ok(errors)) => {
676+
return Err(AssemblerFailure::BadProgram(fail_with_diagnostics(
677+
source_file_body,
678+
errors,
679+
)));
680+
}
681+
AssemblerPass1Or2Output::Pass2Failed(e) => {
682+
return Err(e);
683+
}
684+
AssemblerPass1Or2Output::Success(
652685
errors,
653-
)));
654-
}
655-
let directive = match directive {
656-
None => {
657-
return Err(AssemblerFailure::InternalError(
658-
"assembly pass 2 generated no errors, so it should have generated ouptut code (even if empty)".to_string()
659-
));
686+
_output_options,
687+
Pass2Output {
688+
directive: None, ..
689+
},
690+
) if errors.is_empty() => {
691+
panic!("assembly pass1 generated no errors, a directive should have been returned");
692+
}
693+
AssemblerPass1Or2Output::Success(errors, output_options, p2output) => {
694+
if !errors.is_empty() {
695+
return Err(AssemblerFailure::BadProgram(fail_with_diagnostics(
696+
source_file_body,
697+
errors,
698+
)));
699+
} else {
700+
options = options.merge(output_options);
701+
p2output
702+
}
660703
}
661-
Some(d) => d,
662704
};
663705

664706
// Now we do pass 3, which generates the binary output
665707
let binary = {
666708
let mut listing = Listing::default();
667709
let (binary, final_symbols) = assemble_pass3(
668-
directive,
669-
&mut symbols,
670-
&mut memory_map,
671-
&mut index_register_assigner,
710+
p2output
711+
.directive
712+
.expect("directive should have already been checked for None-ness"),
713+
&mut p2output.symbols,
714+
&mut p2output.memory_map,
715+
&mut p2output.index_register_assigner,
672716
source_file_body,
673717
&mut listing,
674718
)?;
@@ -705,7 +749,7 @@ fn test_assemble_pass1() {
705749
let expected_directive_entry_point = Some(Address::new(Unsigned18Bit::from(0o26_u8)));
706750
let expected_block = ManuscriptBlock {
707751
origin: None,
708-
statements: vec![TaggedProgramInstruction {
752+
sequences: vec![TaggedProgramInstruction {
709753
span: span(0..2),
710754
tags: Vec::new(),
711755
instruction: UntaggedProgramInstruction::from(OneOrMore::new(CommaDelimitedFragment {
@@ -730,7 +774,7 @@ fn test_assemble_pass1() {
730774
Some(SourceFile {
731775
punch: Some(PunchCommand(expected_directive_entry_point)),
732776
blocks: vec![expected_block],
733-
equalities: Default::default(), // no equalities
777+
global_equalities: Default::default(), // no equalities
734778
macros: Default::default(),
735779
}),
736780
OutputOptions { list: false }
@@ -788,3 +832,33 @@ pub fn assemble_file(
788832
let mut writer = BufWriter::new(output_file);
789833
write_user_program(&user_program, &mut writer, output_file_name)
790834
}
835+
836+
#[test]
837+
fn test_duplicate_global_tag() {
838+
let input = concat!("TGX->0\n", "TGX->0\n");
839+
match assemble_nonempty_input(input) {
840+
AssemblerPass1Or2Output::Pass2Failed(AssemblerFailure::BadProgram(errors)) => {
841+
dbg!(&errors);
842+
match errors.as_slice() {
843+
[WithLocation {
844+
inner: ProgramError::SyntaxError { msg, span: _ },
845+
..
846+
}] => {
847+
assert!(msg
848+
.contains("bad symbol definition for TGX: TGX is defined more than once"));
849+
}
850+
other => {
851+
panic!("expected a syntax error report, got {other:?}");
852+
}
853+
}
854+
}
855+
AssemblerPass1Or2Output::Success(errors, _output_options, p2output) => {
856+
dbg!(&errors);
857+
dbg!(&p2output);
858+
panic!("assembler unexpectedly succeeded with a bad input {input}");
859+
}
860+
unexpected => {
861+
panic!("assembly failed, but not in the way expected by this test: {unexpected:?}");
862+
}
863+
}
864+
}

assembler/src/asmlib/driver/tests.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ fn test_assemble_pass1() {
7676
punch: Some(PunchCommand(Some(Address::from(u18!(0o26))))),
7777
blocks: vec![ManuscriptBlock {
7878
origin: None,
79-
statements: [TaggedProgramInstruction {
79+
sequences: [TaggedProgramInstruction {
8080
span: span(0..2),
8181
tags: Vec::new(),
8282
instruction: UntaggedProgramInstruction {
@@ -96,8 +96,8 @@ fn test_assemble_pass1() {
9696
.into_iter()
9797
.collect()
9898
}],
99-
equalities: Default::default(), // no equalities
100-
macros: Default::default(), // no macros
99+
global_equalities: Default::default(), // no equalities
100+
macros: Default::default(), // no macros
101101
})
102102
);
103103
}

0 commit comments

Comments
 (0)