gbas 0.1.0

An assembler for Game Boy
Documentation
use super::context::{EvalContext, SymbolTable};
use super::{traverse_chunk_items, Chunk, Node};
use backend::{BinarySection, RelocExpr};
use diagnostics::{DiagnosticsListener, InternalDiagnostic, Message};
use span::{Source, Span};
use std::vec::IntoIter;
use Width;

impl<S: Span> Chunk<S> {
    pub fn translate(
        &self,
        context: &mut EvalContext<&SymbolTable>,
        diagnostics: &mut impl DiagnosticsListener<S>,
    ) -> BinarySection {
        let mut data = Vec::<u8>::new();
        let origin = self.evaluate_origin(&context);
        context.location = origin.clone();
        traverse_chunk_items(&self.items, context, |item, context| {
            data.extend(item.translate(context, diagnostics))
        });
        BinarySection {
            origin: origin.exact().unwrap() as usize,
            data,
        }
    }
}

impl<S: Span> Node<S> {
    fn translate(
        &self,
        context: &EvalContext<&SymbolTable>,
        diagnostics: &mut impl DiagnosticsListener<S>,
    ) -> IntoIter<u8> {
        match self {
            Node::Byte(value) => vec![*value],
            Node::Embedded(opcode, expr) => {
                let n = expr.evaluate(context).exact().unwrap();
                vec![opcode | ((n as u8) << 3)]
            }
            Node::Expr(expr, width) => {
                resolve_expr_item(&expr, *width, context, diagnostics).into_bytes()
            }
            Node::Label(..) => vec![],
            Node::LdInlineAddr(opcode, expr) => {
                let addr = expr.evaluate(context).exact().unwrap();
                let kind = if addr < 0xff00 {
                    AddrKind::Low
                } else {
                    AddrKind::High
                };
                let opcode = opcode | match kind {
                    AddrKind::Low => 0x0a,
                    AddrKind::High => 0x00,
                };
                let mut bytes = vec![opcode];
                let addr_repr = match kind {
                    AddrKind::Low => Data::Word(addr as u16),
                    AddrKind::High => Data::Byte((addr & 0xff) as u8),
                };
                bytes.extend(addr_repr.into_bytes());
                bytes
            }
        }.into_iter()
    }
}

#[derive(Clone, Copy)]
enum AddrKind {
    Low,
    High,
}

#[derive(Clone, Copy)]
enum Data {
    Byte(u8),
    Word(u16),
}

impl Data {
    fn into_bytes(self) -> Vec<u8> {
        match self {
            Data::Byte(value) => vec![value],
            Data::Word(value) => {
                let low = (value & 0xff) as u8;
                let high = ((value >> 8) & 0xff) as u8;
                vec![low, high]
            }
        }
    }
}

fn resolve_expr_item<S: Span>(
    expr: &RelocExpr<S>,
    width: Width,
    context: &EvalContext<&SymbolTable>,
    diagnostics: &mut impl DiagnosticsListener<S>,
) -> Data {
    let span = expr.span();
    let value = expr
        .evaluate_strictly(context, &mut |symbol, span| {
            diagnostics.emit_diagnostic(InternalDiagnostic::new(
                Message::UnresolvedSymbol {
                    symbol: symbol.to_string(),
                },
                span.clone(),
            ))
        }).exact()
        .unwrap_or(0);
    fit_to_width((value, span), width, diagnostics)
}

fn fit_to_width<SR: Clone>(
    (value, value_ref): (i32, SR),
    width: Width,
    diagnostics: &mut impl DiagnosticsListener<SR>,
) -> Data {
    if !is_in_range(value, width) {
        diagnostics.emit_diagnostic(InternalDiagnostic::new(
            Message::ValueOutOfRange { value, width },
            value_ref,
        ))
    }
    match width {
        Width::Byte => Data::Byte(value as u8),
        Width::Word => Data::Word(value as u16),
    }
}

fn is_in_range(n: i32, width: Width) -> bool {
    match width {
        Width::Byte => is_in_byte_range(n),
        Width::Word => true,
    }
}

fn is_in_byte_range(n: i32) -> bool {
    is_in_i8_range(n) || is_in_u8_range(n)
}

fn is_in_i8_range(n: i32) -> bool {
    n >= i32::from(i8::min_value()) && n <= i32::from(i8::max_value())
}

fn is_in_u8_range(n: i32) -> bool {
    n >= i32::from(u8::min_value()) && n <= i32::from(u8::max_value())
}

#[cfg(test)]
mod tests {
    use super::*;
    use backend::{BinaryOperator, RelocAtom};
    use diagnostics::IgnoreDiagnostics;
    use expr::ExprVariant;
    use std::borrow::Borrow;

    #[test]
    fn translate_ld_deref_addr_a_with_low_addr() {
        test_translation_of_ld_inline_addr(0xe0, 0x2000, [0xea, 0x00, 0x20])
    }

    #[test]
    fn translate_ld_a_deref_addr_with_low_addr() {
        test_translation_of_ld_inline_addr(0xf0, 0x2000, [0xfa, 0x00, 0x20])
    }

    #[test]
    fn translate_ld_deref_addr_a_with_high_addr() {
        test_translation_of_ld_inline_addr(0xe0, 0xff77, [0xe0, 0x77])
    }

    #[test]
    fn translate_ld_a_deref_addr_with_high_addr() {
        test_translation_of_ld_inline_addr(0xf0, 0xff77, [0xf0, 0x77])
    }

    fn test_translation_of_ld_inline_addr(opcode: u8, addr: u16, expected: impl Borrow<[u8]>) {
        let actual = translate_chunk_item(Node::LdInlineAddr(
            opcode,
            RelocAtom::Literal(addr.into()).into(),
        ));
        assert_eq!(actual, expected.borrow())
    }

    #[test]
    fn translate_embedded() {
        let actual = translate_chunk_item(Node::Embedded(0b01_000_110, 4.into()));
        assert_eq!(actual, [0x66])
    }

    #[test]
    fn translate_expr_with_subtraction() {
        let actual = translate_chunk_item(Node::Expr(
            ExprVariant::Binary(
                BinaryOperator::Minus,
                Box::new(4.into()),
                Box::new(3.into()),
            ).into(),
            Width::Byte,
        ));
        assert_eq!(actual, [0x01])
    }

    fn translate_chunk_item<S: Span>(item: Node<S>) -> Vec<u8> {
        use backend::object::resolve::Value;
        use diagnostics;
        item.translate(
            &EvalContext {
                symbols: &SymbolTable::new(),
                location: Value::Unknown,
            },
            &mut diagnostics::IgnoreDiagnostics {},
        ).collect()
    }

    #[test]
    fn set_origin_of_translated_chunk() {
        let addr = 0x7ff0;
        let chunk = Chunk {
            origin: Some(addr.into()),
            items: Vec::new(),
        };
        let translated = translate_without_context(chunk);
        assert_eq!(translated.origin, addr as usize)
    }

    #[test]
    fn translate_expr_with_location_counter() {
        let byte = 0x42;
        let mut chunk = Chunk::new();
        chunk.items.extend(vec![
            Node::Byte(byte),
            Node::Expr(RelocAtom::LocationCounter.into(), Width::Byte),
        ]);
        let binary = translate_without_context(chunk);
        assert_eq!(binary.data, [byte, 0x02])
    }

    #[test]
    fn location_counter_starts_from_chunk_origin() {
        let mut chunk = Chunk::new();
        chunk.origin = Some(0xffe1.into());
        chunk
            .items
            .push(Node::Expr(RelocAtom::LocationCounter.into(), Width::Word));
        let binary = translate_without_context(chunk);
        assert_eq!(binary.data, [0xe3, 0xff])
    }

    fn translate_without_context<S: Span>(chunk: Chunk<S>) -> BinarySection {
        let mut context = EvalContext {
            symbols: &SymbolTable::new(),
            location: 0.into(),
        };
        chunk.translate(&mut context, &mut IgnoreDiagnostics)
    }
}