1use super::{
2    analyze_reloc_expr, CommandArgs, Directive, SemanticActions, SemanticAtom, SemanticExpr,
3};
4use backend;
5use backend::{BinaryOperator, RelocAtom};
6use diagnostics::{InternalDiagnostic, Message};
7use expr::ExprVariant;
8use frontend::session::Session;
9use frontend::syntax::Literal;
10use instruction::RelocExpr;
11use span::Span;
12use std::fmt::Debug;
13use Width;
14
15pub fn analyze_directive<'a, S: Session + 'a>(
16    directive: (Directive, S::Span),
17    args: CommandArgs<S>,
18    actions: &mut SemanticActions<'a, S>,
19) -> Result<(), InternalDiagnostic<S::Span>> {
20    match directive.0 {
21        Directive::Db => analyze_data(Width::Byte, args, actions),
22        Directive::Ds => analyze_ds(directive.1, args, actions),
23        Directive::Dw => analyze_data(Width::Word, args, actions),
24        Directive::Include => analyze_include(directive.1, args, actions),
25        Directive::Org => analyze_org(directive.1, args, actions),
26    }
27}
28
29fn analyze_data<'a, S: Session + 'a>(
30    width: Width,
31    args: CommandArgs<S>,
32    actions: &mut SemanticActions<'a, S>,
33) -> Result<(), InternalDiagnostic<S::Span>> {
34    for arg in args {
35        let expr = analyze_reloc_expr(arg, &mut actions.expr_factory)?;
36        actions.session.emit_item(backend::Item::Data(expr, width))
37    }
38    Ok(())
39}
40
41fn analyze_ds<'a, S: Session + 'a>(
42    span: S::Span,
43    args: CommandArgs<S>,
44    actions: &mut SemanticActions<'a, S>,
45) -> Result<(), InternalDiagnostic<S::Span>> {
46    let arg = single_arg(span, args)?;
47    let count = analyze_reloc_expr(arg, &mut actions.expr_factory)?;
48    actions
49        .session
50        .set_origin(location_counter_plus_expr(count));
51    Ok(())
52}
53
54fn location_counter_plus_expr<S: Clone>(expr: RelocExpr<S>) -> RelocExpr<S> {
55    let span = expr.span.clone();
56    RelocExpr {
57        variant: ExprVariant::Binary(
58            BinaryOperator::Plus,
59            Box::new(RelocExpr {
60                variant: ExprVariant::Atom(RelocAtom::LocationCounter),
61                span: span.clone(),
62            }),
63            Box::new(expr),
64        ),
65        span,
66    }
67}
68
69fn analyze_include<'a, F: Session + 'a>(
70    span: F::Span,
71    args: CommandArgs<F>,
72    actions: &mut SemanticActions<'a, F>,
73) -> Result<(), InternalDiagnostic<F::Span>> {
74    let (path, span) = reduce_include(span, args)?;
75    actions
76        .session
77        .analyze_file(path)
78        .map_err(|err| InternalDiagnostic::new(err.into(), span))
79}
80
81fn reduce_include<I, S>(
82    span: S,
83    args: Vec<SemanticExpr<I, S>>,
84) -> Result<(I, S), InternalDiagnostic<S>>
85where
86    I: Debug + PartialEq,
87    S: Span,
88{
89    let arg = single_arg(span, args)?;
90    match arg.variant {
91        ExprVariant::Atom(SemanticAtom::Literal(Literal::String(path))) => Ok((path, arg.span)),
92        _ => Err(InternalDiagnostic::new(Message::ExpectedString, arg.span)),
93    }
94}
95
96fn analyze_org<'a, S: Session + 'a>(
97    span: S::Span,
98    args: CommandArgs<S>,
99    actions: &mut SemanticActions<'a, S>,
100) -> Result<(), InternalDiagnostic<S::Span>> {
101    let arg = single_arg(span, args)?;
102    let expr = analyze_reloc_expr(arg, &mut actions.expr_factory)?;
103    actions.session.set_origin(expr);
104    Ok(())
105}
106
107fn single_arg<T: Debug + PartialEq, S>(
108    span: S,
109    args: impl IntoIterator<Item = T>,
110) -> Result<T, InternalDiagnostic<S>> {
111    let mut args = args.into_iter();
112    let arg = args.next().ok_or_else(|| {
113        InternalDiagnostic::new(
114            Message::OperandCount {
115                actual: 0,
116                expected: 1,
117            },
118            span,
119        )
120    })?;
121    assert_eq!(args.next(), None);
122    Ok(arg)
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128    use codebase::CodebaseError;
129    use frontend::semantics;
130    use frontend::semantics::tests::*;
131    use frontend::syntax::keyword::{Command, Operand};
132    use frontend::syntax::{CommandContext, ExprAtom, ExprContext, FileContext, StmtContext};
133    use std::borrow::Borrow;
134    use std::io;
135
136    #[test]
137    fn build_include_item() {
138        let filename = "file.asm";
139        let actions = unary_directive(Directive::Include, |arg| {
140            arg.push_atom((ExprAtom::Literal(Literal::String(filename.to_string())), ()));
141        });
142        assert_eq!(actions, [TestOperation::AnalyzeFile(filename.to_string())])
143    }
144
145    #[test]
146    fn set_origin() {
147        let origin = 0x3000;
148        let actions = unary_directive(Directive::Org, |arg| arg.push_atom(mk_literal(origin)));
149        assert_eq!(actions, [TestOperation::SetOrigin(origin.into())])
150    }
151
152    #[test]
153    fn emit_byte_items() {
154        test_data_items_emission(Directive::Db, mk_byte, [0x42, 0x78])
155    }
156
157    #[test]
158    fn emit_word_items() {
159        test_data_items_emission(Directive::Dw, mk_word, [0x4332, 0x780f])
160    }
161
162    fn mk_byte(byte: &i32) -> backend::Item<()> {
163        backend::Item::Data((*byte).into(), Width::Byte)
164    }
165
166    fn mk_word(word: &i32) -> backend::Item<()> {
167        backend::Item::Data((*word).into(), Width::Word)
168    }
169
170    fn test_data_items_emission(
171        directive: Directive,
172        mk_item: impl Fn(&i32) -> backend::Item<()>,
173        data: impl Borrow<[i32]>,
174    ) {
175        let actions = with_directive(directive, |mut command| {
176            for datum in data.borrow().iter() {
177                let mut arg = command.add_argument();
178                arg.push_atom(mk_literal(*datum));
179                command = arg.exit();
180            }
181            command
182        });
183        assert_eq!(
184            actions,
185            data.borrow()
186                .iter()
187                .map(mk_item)
188                .map(TestOperation::EmitItem)
189                .collect::<Vec<_>>()
190        )
191    }
192
193    #[test]
194    fn reserve_3_bytes() {
195        let actions = ds(|arg| arg.push_atom(mk_literal(3)));
196        assert_eq!(
197            actions,
198            [TestOperation::SetOrigin(
199                ExprVariant::Binary(
200                    BinaryOperator::Plus,
201                    Box::new(RelocAtom::LocationCounter.into()),
202                    Box::new(3.into()),
203                ).into()
204            )]
205        )
206    }
207
208    fn mk_literal(n: i32) -> (ExprAtom<String, Literal<String>>, ()) {
209        (ExprAtom::Literal(Literal::Number(n)), ())
210    }
211
212    #[test]
213    fn ds_with_malformed_expr() {
214        let actions =
215            ds(|arg| arg.push_atom((ExprAtom::Literal(Literal::Operand(Operand::A)), ())));
216        assert_eq!(
217            actions,
218            [TestOperation::EmitDiagnostic(InternalDiagnostic::new(
219                Message::KeywordInExpr { keyword: () },
220                (),
221            ))]
222        )
223    }
224
225    #[test]
226    fn ds_without_args() {
227        test_unary_directive_without_args(Directive::Ds)
228    }
229
230    #[test]
231    fn org_without_args() {
232        test_unary_directive_without_args(Directive::Org)
233    }
234
235    #[test]
236    fn include_without_args() {
237        test_unary_directive_without_args(Directive::Include)
238    }
239
240    #[test]
241    fn include_with_number() {
242        let actions = unary_directive(Directive::Include, |arg| arg.push_atom(mk_literal(7)));
243        assert_eq!(
244            actions,
245            [TestOperation::EmitDiagnostic(InternalDiagnostic::new(
246                Message::ExpectedString,
247                (),
248            ))]
249        )
250    }
251
252    #[test]
253    fn data_with_malformed_expr() {
254        let actions = unary_directive(Directive::Db, |arg| {
255            arg.push_atom((ExprAtom::Literal(Literal::Operand(Operand::A)), ()))
256        });
257        assert_eq!(
258            actions,
259            [TestOperation::EmitDiagnostic(InternalDiagnostic::new(
260                Message::KeywordInExpr { keyword: () },
261                (),
262            ))]
263        )
264    }
265
266    #[test]
267    fn include_file_with_invalid_utf8() {
268        let name = "invalid_utf8.s";
269        let mut frontend = TestFrontend::new();
270        frontend.fail(CodebaseError::Utf8Error);
271        {
272            let mut context = SemanticActions::new(&mut frontend)
273                .enter_stmt(None)
274                .enter_command((Command::Directive(Directive::Include), ()))
275                .add_argument();
276            context.push_atom((ExprAtom::Literal(Literal::String(name.into())), ()));
277            context.exit().exit().exit();
278        }
279        assert_eq!(
280            frontend.into_inner(),
281            [
282                TestOperation::AnalyzeFile(name.into()),
283                TestOperation::EmitDiagnostic(InternalDiagnostic::new(Message::InvalidUtf8, ()))
284            ]
285        )
286    }
287
288    #[test]
289    fn include_nonexistent_file() {
290        let name = "nonexistent.s";
291        let message = "some message";
292        let mut frontend = TestFrontend::new();
293        frontend.fail(CodebaseError::IoError(io::Error::new(
294            io::ErrorKind::NotFound,
295            message,
296        )));
297        {
298            let mut context = SemanticActions::new(&mut frontend)
299                .enter_stmt(None)
300                .enter_command((Command::Directive(Directive::Include), ()))
301                .add_argument();
302            context.push_atom((ExprAtom::Literal(Literal::String(name.into())), ()));
303            context.exit().exit().exit();
304        }
305        assert_eq!(
306            frontend.into_inner(),
307            [
308                TestOperation::AnalyzeFile(name.into()),
309                TestOperation::EmitDiagnostic(InternalDiagnostic::new(
310                    Message::IoError {
311                        string: message.to_string()
312                    },
313                    ()
314                ))
315            ]
316        )
317    }
318
319    fn ds(
320        f: impl for<'a> FnOnce(&mut semantics::ExprContext<'a, TestFrontend>),
321    ) -> Vec<TestOperation> {
322        unary_directive(Directive::Ds, f)
323    }
324
325    fn unary_directive<F>(directive: Directive, f: F) -> Vec<TestOperation>
326    where
327        F: for<'a> FnOnce(&mut semantics::ExprContext<'a, TestFrontend>),
328    {
329        with_directive(directive, |command| {
330            let mut arg = command.add_argument();
331            f(&mut arg);
332            arg.exit()
333        })
334    }
335
336    fn test_unary_directive_without_args(directive: Directive) {
337        let actions = with_directive(directive, |command| command);
338        assert_eq!(
339            actions,
340            [TestOperation::EmitDiagnostic(InternalDiagnostic::new(
341                Message::OperandCount {
342                    actual: 0,
343                    expected: 1
344                },
345                (),
346            ))]
347        )
348    }
349
350    fn with_directive<F>(directive: Directive, f: F) -> Vec<TestOperation>
351    where
352        F: for<'a> FnOnce(semantics::CommandActions<'a, TestFrontend>)
353            -> semantics::CommandActions<'a, TestFrontend>,
354    {
355        collect_semantic_actions(|actions| {
356            let command = actions
357                .enter_stmt(None)
358                .enter_command((Command::Directive(directive), ()));
359            f(command).exit().exit()
360        })
361    }
362}