1use backend::{self, BinaryOperator, RelocExpr};
2use diagnostics::{DiagnosticsListener, InternalDiagnostic, Message};
3use expr::ExprVariant;
4use frontend::session::Session;
5use frontend::syntax::{self, keyword::*, ExprAtom, ExprOperator, Token};
6use frontend::{ExprFactory, Literal, StrExprFactory};
7
8mod directive;
9mod instruction;
10mod operand;
11
12mod expr {
13 use expr::Expr;
14 #[cfg(test)]
15 use expr::ExprVariant;
16 use frontend::Literal;
17
18 #[derive(Debug, PartialEq)]
19 pub enum SemanticAtom<I> {
20 Ident(I),
21 Literal(Literal<I>),
22 }
23
24 impl<I> From<Literal<I>> for SemanticAtom<I> {
25 fn from(literal: Literal<I>) -> Self {
26 SemanticAtom::Literal(literal)
27 }
28 }
29
30 #[derive(Debug, PartialEq)]
31 pub enum SemanticUnary {
32 Parentheses,
33 }
34
35 #[derive(Debug, PartialEq)]
36 pub enum SemanticBinary {
37 Plus,
38 }
39
40 pub type SemanticExpr<I, S> = Expr<SemanticAtom<I>, SemanticUnary, SemanticBinary, S>;
41
42 #[cfg(test)]
43 pub type SemanticExprVariant<I, S> =
44 ExprVariant<SemanticAtom<I>, SemanticUnary, SemanticBinary, S>;
45}
46
47use self::expr::*;
48
49pub struct SemanticActions<'a, F: Session + 'a> {
50 session: &'a mut F,
51 expr_factory: StrExprFactory,
52 label: Option<(F::Ident, F::Span)>,
53}
54
55impl<'a, F: Session + 'a> SemanticActions<'a, F> {
56 pub fn new(session: &'a mut F) -> SemanticActions<'a, F> {
57 SemanticActions {
58 session,
59 expr_factory: StrExprFactory::new(),
60 label: None,
61 }
62 }
63
64 fn define_label_if_present(&mut self) {
65 if let Some((label, span)) = self.label.take() {
66 self.session.define_label((label.into(), span))
67 }
68 }
69}
70
71impl<'a, F: Session + 'a> DiagnosticsListener<F::Span> for SemanticActions<'a, F> {
72 fn emit_diagnostic(&mut self, diagnostic: InternalDiagnostic<F::Span>) {
73 self.session.emit_diagnostic(diagnostic)
74 }
75}
76
77impl<'a, F: Session + 'a> syntax::FileContext<F::Ident, Command, Literal<F::Ident>, F::Span>
78 for SemanticActions<'a, F>
79{
80 type StmtContext = Self;
81
82 fn enter_stmt(mut self, label: Option<(F::Ident, F::Span)>) -> Self::StmtContext {
83 self.label = label;
84 self
85 }
86}
87
88impl<'a, F: Session + 'a> syntax::StmtContext<F::Ident, Command, Literal<F::Ident>, F::Span>
89 for SemanticActions<'a, F>
90{
91 type CommandContext = CommandActions<'a, F>;
92 type MacroParamsContext = MacroDefActions<'a, F>;
93 type MacroInvocationContext = MacroInvocationActions<'a, F>;
94 type Parent = Self;
95
96 fn enter_command(mut self, name: (Command, F::Span)) -> Self::CommandContext {
97 self.define_label_if_present();
98 CommandActions::new(name, self)
99 }
100
101 fn enter_macro_def(mut self, keyword: F::Span) -> Self::MacroParamsContext {
102 if self.label.is_none() {
103 self.emit_diagnostic(InternalDiagnostic::new(Message::MacroRequiresName, keyword))
104 }
105 MacroDefActions::new(self.label.take(), self)
106 }
107
108 fn enter_macro_invocation(mut self, name: (F::Ident, F::Span)) -> Self::MacroInvocationContext {
109 self.define_label_if_present();
110 MacroInvocationActions::new(name, self)
111 }
112
113 fn exit(mut self) -> Self::Parent {
114 self.define_label_if_present();
115 self
116 }
117}
118
119pub struct CommandActions<'a, F: Session + 'a> {
120 name: (Command, F::Span),
121 args: CommandArgs<F>,
122 parent: SemanticActions<'a, F>,
123 has_errors: bool,
124}
125
126type CommandArgs<F> = Vec<SemanticExpr<<F as Session>::Ident, <F as Session>::Span>>;
127
128impl<'a, F: Session + 'a> CommandActions<'a, F> {
129 fn new(name: (Command, F::Span), parent: SemanticActions<'a, F>) -> CommandActions<'a, F> {
130 CommandActions {
131 name,
132 args: Vec::new(),
133 parent,
134 has_errors: false,
135 }
136 }
137}
138
139impl<'a, F: Session + 'a> DiagnosticsListener<F::Span> for CommandActions<'a, F> {
140 fn emit_diagnostic(&mut self, diagnostic: InternalDiagnostic<F::Span>) {
141 self.has_errors = true;
142 self.parent.emit_diagnostic(diagnostic)
143 }
144}
145
146impl<'a, F: Session + 'a> syntax::CommandContext<F::Span> for CommandActions<'a, F> {
147 type Ident = F::Ident;
148 type Command = Command;
149 type Literal = Literal<F::Ident>;
150 type ArgContext = ExprContext<'a, F>;
151 type Parent = SemanticActions<'a, F>;
152
153 fn add_argument(self) -> Self::ArgContext {
154 ExprContext {
155 stack: Vec::new(),
156 parent: self,
157 }
158 }
159
160 fn exit(mut self) -> Self::Parent {
161 if !self.has_errors {
162 let result = match self.name {
163 (Command::Directive(directive), span) => {
164 directive::analyze_directive((directive, span), self.args, &mut self.parent)
165 }
166 (Command::Mnemonic(mnemonic), range) => {
167 analyze_mnemonic((mnemonic, range), self.args, &mut self.parent)
168 }
169 };
170 if let Err(diagnostic) = result {
171 self.parent.emit_diagnostic(diagnostic);
172 }
173 }
174 self.parent
175 }
176}
177
178pub struct ExprContext<'a, F: Session + 'a> {
179 stack: Vec<SemanticExpr<F::Ident, F::Span>>,
180 parent: CommandActions<'a, F>,
181}
182
183impl<'a, F: Session + 'a> DiagnosticsListener<F::Span> for ExprContext<'a, F> {
184 fn emit_diagnostic(&mut self, diagnostic: InternalDiagnostic<F::Span>) {
185 self.parent.emit_diagnostic(diagnostic)
186 }
187}
188
189impl<'a, F: Session + 'a> syntax::ExprContext<F::Span> for ExprContext<'a, F> {
190 type Ident = F::Ident;
191 type Literal = Literal<F::Ident>;
192 type Parent = CommandActions<'a, F>;
193
194 fn push_atom(&mut self, atom: (ExprAtom<Self::Ident, Self::Literal>, F::Span)) {
195 self.stack.push(SemanticExpr {
196 variant: ExprVariant::Atom(match atom.0 {
197 ExprAtom::Ident(ident) => SemanticAtom::Ident(ident),
198 ExprAtom::Literal(literal) => SemanticAtom::Literal(literal),
199 }),
200 span: atom.1,
201 })
202 }
203
204 fn apply_operator(&mut self, operator: (ExprOperator, F::Span)) {
205 match operator.0 {
206 ExprOperator::Parentheses => {
207 let inner = self.stack.pop().unwrap_or_else(|| unreachable!());
208 self.stack.push(SemanticExpr {
209 variant: ExprVariant::Unary(SemanticUnary::Parentheses, Box::new(inner)),
210 span: operator.1,
211 })
212 }
213 ExprOperator::Plus => {
214 let rhs = self.stack.pop().unwrap_or_else(|| unreachable!());
215 let lhs = self.stack.pop().unwrap_or_else(|| unreachable!());
216 self.stack.push(SemanticExpr {
217 variant: ExprVariant::Binary(
218 SemanticBinary::Plus,
219 Box::new(lhs),
220 Box::new(rhs),
221 ),
222 span: operator.1,
223 })
224 }
225 }
226 }
227
228 fn exit(mut self) -> Self::Parent {
229 if !self.parent.has_errors {
230 assert_eq!(self.stack.len(), 1);
231 self.parent.args.push(self.stack.pop().unwrap());
232 }
233 self.parent
234 }
235}
236
237fn analyze_mnemonic<'a, F: Session + 'a>(
238 name: (Mnemonic, F::Span),
239 args: CommandArgs<F>,
240 actions: &mut SemanticActions<'a, F>,
241) -> Result<(), InternalDiagnostic<F::Span>> {
242 let instruction =
243 instruction::analyze_instruction(name, args.into_iter(), &mut actions.expr_factory)?;
244 actions
245 .session
246 .emit_item(backend::Item::Instruction(instruction));
247 Ok(())
248}
249
250pub struct MacroDefActions<'a, F: Session + 'a> {
251 name: Option<(F::Ident, F::Span)>,
252 params: Vec<(F::Ident, F::Span)>,
253 tokens: Vec<(Token<F::Ident>, F::Span)>,
254 parent: SemanticActions<'a, F>,
255}
256
257impl<'a, F: Session + 'a> MacroDefActions<'a, F> {
258 fn new(
259 name: Option<(F::Ident, F::Span)>,
260 parent: SemanticActions<'a, F>,
261 ) -> MacroDefActions<'a, F> {
262 MacroDefActions {
263 name,
264 params: Vec::new(),
265 tokens: Vec::new(),
266 parent,
267 }
268 }
269}
270
271impl<'a, F: Session + 'a> DiagnosticsListener<F::Span> for MacroDefActions<'a, F> {
272 fn emit_diagnostic(&mut self, diagnostic: InternalDiagnostic<F::Span>) {
273 self.parent.emit_diagnostic(diagnostic)
274 }
275}
276
277impl<'a, F: Session + 'a> syntax::MacroParamsContext<F::Span> for MacroDefActions<'a, F> {
278 type Ident = F::Ident;
279 type Command = Command;
280 type Literal = Literal<F::Ident>;
281 type MacroBodyContext = Self;
282 type Parent = SemanticActions<'a, F>;
283
284 fn add_parameter(&mut self, param: (Self::Ident, F::Span)) {
285 self.params.push(param)
286 }
287
288 fn exit(self) -> Self::MacroBodyContext {
289 self
290 }
291}
292
293impl<'a, F: Session + 'a> syntax::TokenSeqContext<F::Span> for MacroDefActions<'a, F> {
294 type Token = Token<F::Ident>;
295 type Parent = SemanticActions<'a, F>;
296
297 fn push_token(&mut self, token: (Self::Token, F::Span)) {
298 self.tokens.push(token)
299 }
300
301 fn exit(self) -> Self::Parent {
302 if let Some(name) = self.name {
303 self.parent
304 .session
305 .define_macro(name, self.params, self.tokens)
306 }
307 self.parent
308 }
309}
310
311pub struct MacroInvocationActions<'a, F: Session + 'a> {
312 name: (F::Ident, F::Span),
313 args: Vec<super::TokenSeq<F::Ident, F::Span>>,
314 parent: SemanticActions<'a, F>,
315}
316
317impl<'a, F: Session + 'a> MacroInvocationActions<'a, F> {
318 fn new(
319 name: (F::Ident, F::Span),
320 parent: SemanticActions<'a, F>,
321 ) -> MacroInvocationActions<'a, F> {
322 MacroInvocationActions {
323 name,
324 args: Vec::new(),
325 parent,
326 }
327 }
328
329 fn push_arg(&mut self, arg: Vec<(Token<F::Ident>, F::Span)>) {
330 self.args.push(arg)
331 }
332}
333
334impl<'a, F: Session + 'a> DiagnosticsListener<F::Span> for MacroInvocationActions<'a, F> {
335 fn emit_diagnostic(&mut self, diagnostic: InternalDiagnostic<F::Span>) {
336 self.parent.emit_diagnostic(diagnostic)
337 }
338}
339
340impl<'a, F: Session + 'a> syntax::MacroInvocationContext<F::Span>
341 for MacroInvocationActions<'a, F>
342{
343 type Token = Token<F::Ident>;
344 type Parent = SemanticActions<'a, F>;
345 type MacroArgContext = MacroArgContext<'a, F>;
346
347 fn enter_macro_arg(self) -> Self::MacroArgContext {
348 MacroArgContext::new(self)
349 }
350
351 fn exit(self) -> Self::Parent {
352 self.parent.session.invoke_macro(self.name, self.args);
353 self.parent
354 }
355}
356
357pub struct MacroArgContext<'a, F: Session + 'a> {
358 tokens: Vec<(Token<F::Ident>, F::Span)>,
359 parent: MacroInvocationActions<'a, F>,
360}
361
362impl<'a, F: Session + 'a> MacroArgContext<'a, F> {
363 fn new(parent: MacroInvocationActions<'a, F>) -> MacroArgContext<'a, F> {
364 MacroArgContext {
365 tokens: Vec::new(),
366 parent,
367 }
368 }
369}
370
371impl<'a, F: Session + 'a> DiagnosticsListener<F::Span> for MacroArgContext<'a, F> {
372 fn emit_diagnostic(&mut self, diagnostic: InternalDiagnostic<F::Span>) {
373 self.parent.parent.session.emit_diagnostic(diagnostic)
374 }
375}
376
377impl<'a, F: Session + 'a> syntax::TokenSeqContext<F::Span> for MacroArgContext<'a, F> {
378 type Token = Token<F::Ident>;
379 type Parent = MacroInvocationActions<'a, F>;
380
381 fn push_token(&mut self, token: (Self::Token, F::Span)) {
382 self.tokens.push(token)
383 }
384
385 fn exit(mut self) -> Self::Parent {
386 self.parent.push_arg(self.tokens);
387 self.parent
388 }
389}
390
391fn analyze_reloc_expr<I: Into<String>, S: Clone>(
392 expr: SemanticExpr<I, S>,
393 factory: &mut impl ExprFactory,
394) -> Result<RelocExpr<S>, InternalDiagnostic<S>> {
395 match expr.variant {
396 ExprVariant::Atom(SemanticAtom::Ident(ident)) => Ok(factory.mk_symbol((ident, expr.span))),
397 ExprVariant::Atom(SemanticAtom::Literal(Literal::Number(n))) => {
398 Ok(factory.mk_literal((n, expr.span)))
399 }
400 ExprVariant::Atom(SemanticAtom::Literal(Literal::Operand(_))) => {
401 Err(InternalDiagnostic::new(
402 Message::KeywordInExpr {
403 keyword: expr.span.clone(),
404 },
405 expr.span,
406 ))
407 }
408 ExprVariant::Atom(SemanticAtom::Literal(Literal::String(_))) => Err(
409 InternalDiagnostic::new(Message::StringInInstruction, expr.span),
410 ),
411 ExprVariant::Unary(SemanticUnary::Parentheses, expr) => analyze_reloc_expr(*expr, factory),
412 ExprVariant::Binary(SemanticBinary::Plus, left, right) => {
413 let left = analyze_reloc_expr(*left, factory)?;
414 let right = analyze_reloc_expr(*right, factory)?;
415 Ok(RelocExpr {
416 variant: ExprVariant::Binary(BinaryOperator::Plus, Box::new(left), Box::new(right)),
417 span: expr.span,
418 })
419 }
420 }
421}
422
423#[cfg(test)]
424mod tests {
425 use super::*;
426
427 use backend::RelocAtom;
428 use codebase::CodebaseError;
429 use diagnostics::{InternalDiagnostic, Message};
430 use frontend::syntax::{
431 keyword::Operand, CommandContext, ExprContext, FileContext, MacroInvocationContext,
432 MacroParamsContext, StmtContext, TokenSeqContext,
433 };
434 use instruction::RelocExpr;
435 use std::borrow::Borrow;
436 use std::cell::RefCell;
437 use Width;
438
439 pub struct TestFrontend {
440 operations: RefCell<Vec<TestOperation>>,
441 error: Option<CodebaseError>,
442 }
443
444 impl TestFrontend {
445 pub fn new() -> TestFrontend {
446 TestFrontend {
447 operations: RefCell::new(Vec::new()),
448 error: None,
449 }
450 }
451
452 pub fn fail(&mut self, error: CodebaseError) {
453 self.error = Some(error)
454 }
455
456 pub fn into_inner(self) -> Vec<TestOperation> {
457 self.operations.into_inner()
458 }
459 }
460
461 #[derive(Debug, PartialEq)]
462 pub enum TestOperation {
463 AnalyzeFile(String),
464 InvokeMacro(String, Vec<Vec<Token<String>>>),
465 DefineMacro(String, Vec<String>, Vec<Token<String>>),
466 EmitDiagnostic(InternalDiagnostic<()>),
467 EmitItem(backend::Item<()>),
468 Label(String),
469 SetOrigin(RelocExpr<()>),
470 }
471
472 impl Session for TestFrontend {
473 type Ident = String;
474 type Span = ();
475
476 fn analyze_file(&mut self, path: Self::Ident) -> Result<(), CodebaseError> {
477 self.operations
478 .borrow_mut()
479 .push(TestOperation::AnalyzeFile(path));
480 match self.error.take() {
481 Some(error) => Err(error),
482 None => Ok(()),
483 }
484 }
485
486 fn invoke_macro(
487 &mut self,
488 name: (Self::Ident, Self::Span),
489 args: Vec<Vec<(Token<Self::Ident>, Self::Span)>>,
490 ) {
491 self.operations
492 .borrow_mut()
493 .push(TestOperation::InvokeMacro(
494 name.0,
495 args.into_iter()
496 .map(|arg| arg.into_iter().map(|(token, _)| token).collect())
497 .collect(),
498 ))
499 }
500
501 fn emit_diagnostic(&mut self, diagnostic: InternalDiagnostic<Self::Span>) {
502 self.operations
503 .borrow_mut()
504 .push(TestOperation::EmitDiagnostic(diagnostic))
505 }
506
507 fn emit_item(&mut self, item: backend::Item<()>) {
508 self.operations
509 .borrow_mut()
510 .push(TestOperation::EmitItem(item))
511 }
512
513 fn define_label(&mut self, (label, _): (String, Self::Span)) {
514 self.operations
515 .borrow_mut()
516 .push(TestOperation::Label(label))
517 }
518
519 fn define_macro(
520 &mut self,
521 (name, _): (impl Into<String>, Self::Span),
522 params: Vec<(String, Self::Span)>,
523 tokens: Vec<(Token<String>, ())>,
524 ) {
525 self.operations
526 .borrow_mut()
527 .push(TestOperation::DefineMacro(
528 name.into(),
529 params.into_iter().map(|(s, _)| s).collect(),
530 tokens.into_iter().map(|(t, _)| t).collect(),
531 ))
532 }
533
534 fn set_origin(&mut self, origin: RelocExpr<()>) {
535 self.operations
536 .borrow_mut()
537 .push(TestOperation::SetOrigin(origin))
538 }
539 }
540
541 #[test]
542 fn emit_ld_b_deref_hl() {
543 use instruction::*;
544 let actions = collect_semantic_actions(|actions| {
545 let mut command = actions
546 .enter_stmt(None)
547 .enter_command((Command::Mnemonic(Mnemonic::Ld), ()));
548 let mut arg1 = command.add_argument();
549 arg1.push_atom((ExprAtom::Literal(Literal::Operand(Operand::B)), ()));
550 command = arg1.exit();
551 let mut arg2 = command.add_argument();
552 arg2.push_atom((ExprAtom::Literal(Literal::Operand(Operand::Hl)), ()));
553 arg2.apply_operator((ExprOperator::Parentheses, ()));
554 arg2.exit().exit().exit()
555 });
556 assert_eq!(
557 actions,
558 [TestOperation::EmitItem(backend::Item::Instruction(
559 Instruction::Ld(Ld::Simple(SimpleOperand::B, SimpleOperand::DerefHl))
560 ))]
561 )
562 }
563
564 #[test]
565 fn emit_rst_1_plus_1() {
566 use instruction::*;
567 let actions = collect_semantic_actions(|actions| {
568 let command = actions
569 .enter_stmt(None)
570 .enter_command((Command::Mnemonic(Mnemonic::Rst), ()));
571 let mut expr = command.add_argument();
572 expr.push_atom((ExprAtom::Literal(Literal::Number(1)), ()));
573 expr.push_atom((ExprAtom::Literal(Literal::Number(1)), ()));
574 expr.apply_operator((ExprOperator::Plus, ()));
575 expr.exit().exit().exit()
576 });
577 assert_eq!(
578 actions,
579 [TestOperation::EmitItem(backend::Item::Instruction(
580 Instruction::Rst(
581 ExprVariant::Binary(
582 BinaryOperator::Plus,
583 Box::new(1.into()),
584 Box::new(1.into()),
585 ).into()
586 )
587 ))]
588 )
589 }
590
591 #[test]
592 fn emit_label_word() {
593 let label = "my_label";
594 let actions = collect_semantic_actions(|actions| {
595 let mut arg = actions
596 .enter_stmt(None)
597 .enter_command((Command::Directive(Directive::Dw), ()))
598 .add_argument();
599 arg.push_atom((ExprAtom::Ident(label.to_string()), ()));
600 arg.exit().exit().exit()
601 });
602 assert_eq!(
603 actions,
604 [TestOperation::EmitItem(backend::Item::Data(
605 RelocAtom::Symbol(label.to_string()).into(),
606 Width::Word
607 ))]
608 );
609 }
610
611 #[test]
612 fn analyze_label() {
613 let label = "label";
614 let actions = collect_semantic_actions(|actions| {
615 actions.enter_stmt(Some((label.to_string(), ()))).exit()
616 });
617 assert_eq!(actions, [TestOperation::Label(label.to_string())])
618 }
619
620 #[test]
621 fn define_nullary_macro() {
622 test_macro_definition(
623 "my_macro",
624 [],
625 [
626 Token::Command(Command::Mnemonic(Mnemonic::Xor)),
627 Token::Literal(Literal::Operand(Operand::A)),
628 ],
629 )
630 }
631
632 #[test]
633 fn define_unary_macro() {
634 let param = "reg";
635 test_macro_definition(
636 "my_xor",
637 [param],
638 [
639 Token::Command(Command::Mnemonic(Mnemonic::Xor)),
640 Token::Ident(param.to_string()),
641 ],
642 )
643 }
644
645 #[test]
646 fn define_nameless_macro() {
647 let actions = collect_semantic_actions(|actions| {
648 let params = actions.enter_stmt(None).enter_macro_def(());
649 TokenSeqContext::exit(MacroParamsContext::exit(params))
650 });
651 assert_eq!(
652 actions,
653 [TestOperation::EmitDiagnostic(InternalDiagnostic::new(
654 Message::MacroRequiresName,
655 ()
656 ))]
657 )
658 }
659
660 fn test_macro_definition(
661 name: &str,
662 params: impl Borrow<[&'static str]>,
663 body: impl Borrow<[Token<String>]>,
664 ) {
665 let actions = collect_semantic_actions(|actions| {
666 let mut params_actions = actions
667 .enter_stmt(Some((name.to_string(), ())))
668 .enter_macro_def(());
669 for param in params.borrow().iter().map(|t| (t.to_string(), ())) {
670 params_actions.add_parameter(param)
671 }
672 let mut token_seq_actions = MacroParamsContext::exit(params_actions);
673 for token in body.borrow().iter().cloned().map(|t| (t, ())) {
674 token_seq_actions.push_token(token)
675 }
676 TokenSeqContext::exit(token_seq_actions)
677 });
678 assert_eq!(
679 actions,
680 [TestOperation::DefineMacro(
681 name.to_string(),
682 params.borrow().iter().cloned().map(String::from).collect(),
683 body.borrow().iter().cloned().collect()
684 )]
685 )
686 }
687
688 #[test]
689 fn invoke_nullary_macro() {
690 let name = "my_macro";
691 let actions = collect_semantic_actions(|actions| {
692 let invocation = actions
693 .enter_stmt(None)
694 .enter_macro_invocation((name.to_string(), ()));
695 invocation.exit().exit()
696 });
697 assert_eq!(
698 actions,
699 [TestOperation::InvokeMacro(name.to_string(), Vec::new())]
700 )
701 }
702
703 #[test]
704 fn invoke_unary_macro() {
705 let name = "my_macro";
706 let arg_token = Token::Literal(Literal::Operand(Operand::A));
707 let actions = collect_semantic_actions(|actions| {
708 let mut invocation = actions
709 .enter_stmt(None)
710 .enter_macro_invocation((name.to_string(), ()));
711 invocation = {
712 let mut arg = invocation.enter_macro_arg();
713 arg.push_token((arg_token.clone(), ()));
714 arg.exit()
715 };
716 invocation.exit().exit()
717 });
718 assert_eq!(
719 actions,
720 [TestOperation::InvokeMacro(
721 name.to_string(),
722 vec![vec![arg_token]]
723 )]
724 )
725 }
726
727 #[test]
728 fn diagnose_wrong_operand_count() {
729 let actions = collect_semantic_actions(|actions| {
730 let mut arg = actions
731 .enter_stmt(None)
732 .enter_command((Command::Mnemonic(Mnemonic::Nop), ()))
733 .add_argument();
734 let literal_a = Literal::Operand(Operand::A);
735 arg.push_atom((ExprAtom::Literal(literal_a), ()));
736 arg.exit().exit().exit()
737 });
738 assert_eq!(
739 actions,
740 [TestOperation::EmitDiagnostic(InternalDiagnostic::new(
741 Message::OperandCount {
742 actual: 1,
743 expected: 0
744 },
745 ()
746 ))]
747 )
748 }
749
750 #[test]
751 fn diagnose_parsing_error() {
752 let diagnostic = InternalDiagnostic::new(Message::UnexpectedToken { token: () }, ());
753 let actions = collect_semantic_actions(|actions| {
754 let mut stmt = actions.enter_stmt(None);
755 stmt.emit_diagnostic(diagnostic.clone());
756 stmt.exit()
757 });
758 assert_eq!(actions, [TestOperation::EmitDiagnostic(diagnostic)])
759 }
760
761 #[test]
762 fn recover_from_malformed_expr() {
763 let diagnostic = InternalDiagnostic::new(Message::UnexpectedToken { token: () }, ());
764 let actions = collect_semantic_actions(|file| {
765 let mut expr = file
766 .enter_stmt(None)
767 .enter_command((Command::Mnemonic(Mnemonic::Add), ()))
768 .add_argument();
769 expr.emit_diagnostic(diagnostic.clone());
770 expr.exit().exit().exit()
771 });
772 assert_eq!(actions, [TestOperation::EmitDiagnostic(diagnostic)])
773 }
774
775 pub fn collect_semantic_actions<F>(f: F) -> Vec<TestOperation>
776 where
777 F: for<'a> FnOnce(SemanticActions<'a, TestFrontend>) -> SemanticActions<'a, TestFrontend>,
778 {
779 let mut operations = TestFrontend::new();
780 f(SemanticActions::new(&mut operations));
781 operations.operations.into_inner()
782 }
783}