gbas/frontend/semantics/
mod.rs

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}