gbas 0.1.0

An assembler for Game Boy
Documentation
pub use super::context::{EvalContext, SymbolTable};

use super::context::ChunkSize;
use backend::{Node, Object, RelocAtom, RelocExpr};
use expr::ExprVariant;
use span::{Source, Span};
use std::borrow::Borrow;
use std::ops::{Add, AddAssign, Sub};

#[derive(Clone, Debug, PartialEq)]
pub enum Value {
    Range { min: i32, max: i32 },
    Unknown,
}

impl Value {
    pub fn exact(&self) -> Option<i32> {
        match *self {
            Value::Range { min, max } if min == max => Some(min),
            _ => None,
        }
    }
}

impl From<i32> for Value {
    fn from(n: i32) -> Self {
        Value::Range { min: n, max: n }
    }
}

impl AddAssign<Value> for Value {
    fn add_assign(&mut self, rhs: Value) {
        match (self, rhs) {
            (
                Value::Range { min, max },
                Value::Range {
                    min: rhs_min,
                    max: rhs_max,
                },
            ) => {
                *min += rhs_min;
                *max += rhs_max;
            }
            (this, _) => *this = Value::Unknown,
        }
    }
}

impl<T: Into<Value>> Add<T> for Value {
    type Output = Value;
    fn add(mut self, rhs: T) -> Self::Output {
        self += rhs.into();
        self
    }
}

impl Sub<Value> for Value {
    type Output = Value;
    fn sub(self, rhs: Value) -> Self::Output {
        match (self, rhs) {
            (
                Value::Range { min, max },
                Value::Range {
                    min: rhs_min,
                    max: rhs_max,
                },
            ) => Value::Range {
                min: min - rhs_max,
                max: max - rhs_min,
            },
            _ => Value::Unknown,
        }
    }
}

pub fn resolve_symbols<S: Span>(object: &Object<S>) -> SymbolTable {
    let mut symbols = collect_symbols(object);
    refine_symbols(object, &mut symbols);
    symbols
}

fn collect_symbols<S: Span>(object: &Object<S>) -> SymbolTable {
    let mut symbols = SymbolTable::new();
    (0..object.chunks.len()).for_each(|i| symbols.define(ChunkSize(i), Value::Unknown));
    {
        let mut context = EvalContext {
            symbols: &mut symbols,
            location: Value::Unknown,
        };
        for (i, chunk) in (&object.chunks).into_iter().enumerate() {
            let size = chunk.traverse(&mut context, |item, context| {
                if let Node::Label(symbol, _) = item {
                    context
                        .symbols
                        .define(symbol.as_str(), context.location.clone())
                }
            });
            context.symbols.refine(ChunkSize(i), size);
        }
    }
    symbols
}

fn refine_symbols<S: Span>(object: &Object<S>, symbols: &mut SymbolTable) -> i32 {
    let mut refinements = 0;
    let context = &mut EvalContext {
        symbols,
        location: Value::Unknown,
    };
    for (i, chunk) in (&object.chunks).into_iter().enumerate() {
        let size = chunk.traverse(context, |item, context| {
            if let Node::Label(symbol, _) = item {
                refinements += context
                    .symbols
                    .refine(symbol.as_str(), context.location.clone())
                    as i32
            }
        });
        refinements += context.symbols.refine(ChunkSize(i), size) as i32
    }
    refinements
}

impl<S: Span> RelocExpr<S> {
    pub fn evaluate<ST: Borrow<SymbolTable>>(&self, context: &EvalContext<ST>) -> Value {
        self.evaluate_strictly(context, &mut |_: &str, _: &S| ())
    }

    pub fn evaluate_strictly<ST, F>(
        &self,
        context: &EvalContext<ST>,
        on_undefined_symbol: &mut F,
    ) -> Value
    where
        ST: Borrow<SymbolTable>,
        F: FnMut(&str, &S),
    {
        match &self.variant {
            ExprVariant::Unary(_, _) => unreachable!(),
            ExprVariant::Binary(operator, lhs, rhs) => {
                use backend::BinaryOperator;
                let lhs = lhs.evaluate_strictly(context, on_undefined_symbol);
                let rhs = rhs.evaluate_strictly(context, on_undefined_symbol);
                match operator {
                    BinaryOperator::Minus => lhs - rhs,
                    BinaryOperator::Plus => lhs + rhs,
                }
            }
            ExprVariant::Atom(RelocAtom::Literal(value)) => (*value).into(),
            ExprVariant::Atom(RelocAtom::LocationCounter) => context.location.clone(),
            ExprVariant::Atom(RelocAtom::Symbol(symbol)) => context
                .symbols
                .borrow()
                .get(symbol.as_str())
                .cloned()
                .unwrap_or_else(|| {
                    on_undefined_symbol(&symbol, &self.span());
                    Value::Unknown
                }),
        }
    }
}

impl<S: Span> Node<S> {
    pub fn size<ST: Borrow<SymbolTable>>(&self, context: &EvalContext<ST>) -> Value {
        match self {
            Node::Byte(_) | Node::Embedded(..) => 1.into(),
            Node::Expr(_, width) => width.len().into(),
            Node::Label(..) => 0.into(),
            Node::LdInlineAddr(_, expr) => match expr.evaluate(context) {
                Value::Range { min, .. } if min >= 0xff00 => 2.into(),
                Value::Range { max, .. } if max < 0xff00 => 3.into(),
                _ => Value::Range { min: 2, max: 3 },
            },
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use backend::object::Chunk;
    use backend::{object::ObjectBuilder, Backend};

    #[test]
    fn label_defined_as_chunk_origin_plus_offset() {
        let label = "label";
        let addr = 0xffe1;
        let mut builder = ObjectBuilder::new();
        builder.set_origin(addr.into());
        builder.add_label((label, ()));
        let object = builder.into_object();
        let symbols = resolve_symbols(&object);
        assert_eq!(symbols.get(label), Some(&addr.into()))
    }

    #[test]
    fn empty_chunk_has_size_zero() {
        assert_chunk_size(0, |_| ())
    }

    #[test]
    fn chunk_with_one_byte_has_size_one() {
        assert_chunk_size(1, |section| section.items.push(Node::Byte(0x42)));
    }

    #[test]
    fn chunk_with_const_inline_addr_ld_has_size_two() {
        test_chunk_size_with_literal_ld_inline_addr(0xff00, 2)
    }

    #[test]
    fn chunk_with_const_inline_addr_ld_has_size_three() {
        test_chunk_size_with_literal_ld_inline_addr(0x1234, 3)
    }

    fn test_chunk_size_with_literal_ld_inline_addr(addr: i32, expected: i32) {
        assert_chunk_size(expected, |section| {
            section.items.push(Node::LdInlineAddr(0, addr.into()))
        });
    }

    #[test]
    fn ld_inline_addr_with_symbol_after_instruction_has_size_three() {
        assert_chunk_size(3, |section| {
            section.items.extend(
                [
                    Node::LdInlineAddr(0, RelocAtom::Symbol("label".to_string()).into()),
                    Node::Label("label".to_string(), ()),
                ]
                    .iter()
                    .cloned(),
            )
        })
    }

    fn assert_chunk_size(expected: impl Into<Value>, f: impl FnOnce(&mut Chunk<()>)) {
        let mut object = Object::<()>::new();
        object.add_chunk();
        f(&mut object.chunks[0]);
        let symbols = resolve_symbols(&object);
        assert_eq!(symbols.get(ChunkSize(0)).cloned(), Some(expected.into()))
    }
}