gbas 0.1.0

An assembler for Game Boy
Documentation
use super::{analyze_reloc_expr, ExprVariant, SemanticAtom, SemanticExpr, SemanticUnary};
use diagnostics::{InternalDiagnostic, KeywordOperandCategory, Message};
use frontend::syntax::keyword as kw;
use frontend::syntax::Literal;
use frontend::ExprFactory;
use instruction::{Condition, PtrReg, Reg16, RegPair, RelocExpr, SimpleOperand};
use span::{Source, Span};

#[derive(Debug, PartialEq)]
pub enum Operand<R> {
    Atom(AtomKind, R),
    Const(RelocExpr<R>),
    Deref(RelocExpr<R>),
}

impl<S: Span> Source for Operand<S> {
    type Span = S;
    fn span(&self) -> Self::Span {
        match self {
            Operand::Atom(_, span) => (*span).clone(),
            Operand::Const(expr) | Operand::Deref(expr) => expr.span(),
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum AtomKind {
    Simple(SimpleOperand),
    Condition(Condition),
    Reg16(Reg16),
    RegPair(RegPair),
    DerefPtrReg(PtrReg),
    DerefC,
}

#[derive(Clone, Copy)]
pub enum Context {
    Branch,
    Stack,
    Other,
}

type OperandResult<S> = Result<Operand<S>, InternalDiagnostic<S>>;

pub fn analyze_operand<I: Into<String>, S: Clone>(
    expr: SemanticExpr<I, S>,
    context: Context,
    factory: &mut impl ExprFactory,
) -> OperandResult<S> {
    match expr.variant {
        ExprVariant::Atom(SemanticAtom::Literal(Literal::Operand(keyword))) => {
            analyze_keyword_operand((keyword, expr.span), context)
        }
        ExprVariant::Unary(SemanticUnary::Parentheses, inner) => {
            analyze_deref_operand(*inner, expr.span, factory)
        }
        _ => Ok(Operand::Const(analyze_reloc_expr(expr, factory)?)),
    }
}

fn analyze_deref_operand<I: Into<String>, S: Clone>(
    expr: SemanticExpr<I, S>,
    deref_span: S,
    factory: &mut impl ExprFactory,
) -> OperandResult<S> {
    match expr.variant {
        ExprVariant::Atom(SemanticAtom::Literal(Literal::Operand(keyword))) => {
            analyze_deref_operand_keyword((keyword, expr.span), deref_span)
        }
        _ => Ok(Operand::Deref(analyze_reloc_expr(expr, factory)?)),
    }
}

fn analyze_deref_operand_keyword<SI>(keyword: (kw::Operand, SI), deref: SI) -> OperandResult<SI> {
    match try_deref_operand_keyword(keyword.0) {
        Ok(atom) => Ok(Operand::Atom(atom, deref)),
        Err(category) => Err(InternalDiagnostic::new(
            Message::CannotDereference {
                category,
                operand: keyword.1,
            },
            deref,
        )),
    }
}

fn try_deref_operand_keyword(keyword: kw::Operand) -> Result<AtomKind, KeywordOperandCategory> {
    use self::kw::Operand::*;
    match keyword {
        Bc => Ok(AtomKind::DerefPtrReg(PtrReg::Bc)),
        C => Ok(AtomKind::DerefC),
        De => Ok(AtomKind::DerefPtrReg(PtrReg::De)),
        Hl => Ok(AtomKind::Simple(SimpleOperand::DerefHl)),
        Hld => Ok(AtomKind::DerefPtrReg(PtrReg::Hld)),
        Hli => Ok(AtomKind::DerefPtrReg(PtrReg::Hli)),
        A | B | D | E | H | L | Sp => Err(KeywordOperandCategory::Reg),
        Af => Err(KeywordOperandCategory::RegPair),
        Nc | Nz | Z => Err(KeywordOperandCategory::ConditionCode),
    }
}

fn analyze_keyword_operand<S: Clone>(
    (keyword, span): (kw::Operand, S),
    context: Context,
) -> OperandResult<S> {
    use self::kw::Operand::*;
    use self::Context::*;
    let kind = match keyword {
        A => AtomKind::Simple(SimpleOperand::A),
        Af => AtomKind::RegPair(RegPair::Af),
        B => AtomKind::Simple(SimpleOperand::B),
        Bc => match context {
            Stack => AtomKind::RegPair(RegPair::Bc),
            _ => AtomKind::Reg16(Reg16::Bc),
        },
        C => match context {
            Branch => AtomKind::Condition(Condition::C),
            _ => AtomKind::Simple(SimpleOperand::C),
        },
        D => AtomKind::Simple(SimpleOperand::D),
        De => match context {
            Stack => AtomKind::RegPair(RegPair::De),
            _ => AtomKind::Reg16(Reg16::De),
        },
        E => AtomKind::Simple(SimpleOperand::E),
        H => AtomKind::Simple(SimpleOperand::H),
        Hl => match context {
            Stack => AtomKind::RegPair(RegPair::Hl),
            _ => AtomKind::Reg16(Reg16::Hl),
        },
        Hld | Hli => Err(InternalDiagnostic::new(
            Message::MustBeDeref {
                operand: span.clone(),
            },
            span.clone(),
        ))?,
        L => AtomKind::Simple(SimpleOperand::L),
        Nc => AtomKind::Condition(Condition::Nc),
        Nz => AtomKind::Condition(Condition::Nz),
        Sp => AtomKind::Reg16(Reg16::Sp),
        Z => AtomKind::Condition(Condition::Z),
    };
    Ok(Operand::Atom(kind, span))
}

pub struct OperandCounter<I> {
    operands: I,
    count: usize,
}

impl<I: Iterator<Item = Result<T, E>>, T, E> OperandCounter<I> {
    pub fn new(operands: I) -> OperandCounter<I> {
        OperandCounter { operands, count: 0 }
    }

    pub fn seen(&self) -> usize {
        self.count
    }

    pub fn next(&mut self) -> Result<Option<T>, E> {
        self.operands.next().map_or(Ok(None), |operand| {
            self.count += 1;
            operand.map(Some)
        })
    }

    pub fn check_for_unexpected_operands<S>(self, span: S) -> Result<(), InternalDiagnostic<S>> {
        let expected = self.count;
        let extra = self.operands.count();
        let actual = expected + extra;
        if actual == expected {
            Ok(())
        } else {
            Err(InternalDiagnostic::new(
                Message::OperandCount { actual, expected },
                span,
            ))
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use backend::RelocAtom;

    #[test]
    fn analyze_deref_bc() {
        analyze_deref_ptr_reg(PtrReg::Bc)
    }

    #[test]
    fn analyze_deref_de() {
        analyze_deref_ptr_reg(PtrReg::De)
    }

    #[test]
    fn analyze_deref_hli() {
        analyze_deref_ptr_reg(PtrReg::Hli)
    }

    #[test]
    fn analyze_deref_hld() {
        analyze_deref_ptr_reg(PtrReg::Hld)
    }

    fn analyze_deref_ptr_reg(ptr_reg: PtrReg) {
        let expr = SemanticExpr::<String, _> {
            variant: ExprVariant::Unary(
                SemanticUnary::Parentheses,
                Box::new(SemanticExpr::from_atom(
                    SemanticAtom::Literal(Literal::Operand(ptr_reg.into())),
                    0,
                )),
            ),
            span: 1,
        };
        assert_eq!(
            analyze_operand(expr, Context::Other),
            Ok(Operand::Atom(AtomKind::DerefPtrReg(ptr_reg), 1))
        )
    }

    fn analyze_operand<I: Into<String>, S: Clone>(
        expr: SemanticExpr<I, S>,
        context: Context,
    ) -> OperandResult<S> {
        use frontend::StrExprFactory;
        let mut factory = StrExprFactory::new();
        super::analyze_operand(expr, context, &mut factory)
    }

    #[test]
    fn analyze_deref_af() {
        let parsed_expr = SemanticExpr::<String, _> {
            variant: ExprVariant::Unary(
                SemanticUnary::Parentheses,
                Box::new(SemanticExpr::from_atom(
                    SemanticAtom::Literal(Literal::Operand(kw::Operand::Af)),
                    0,
                )),
            ),
            span: 1,
        };
        assert_eq!(
            analyze_operand(parsed_expr, Context::Other),
            Err(InternalDiagnostic::new(
                Message::CannotDereference {
                    category: KeywordOperandCategory::RegPair,
                    operand: 0,
                },
                1
            ))
        )
    }

    #[test]
    fn analyze_repeated_parentheses() {
        let n = 0x42;
        let span = 0;
        let parsed_expr = SemanticExpr::<String, _> {
            variant: ExprVariant::Unary(
                SemanticUnary::Parentheses,
                Box::new(SemanticExpr {
                    variant: ExprVariant::Unary(
                        SemanticUnary::Parentheses,
                        Box::new(SemanticExpr::from_atom(
                            SemanticAtom::Literal(Literal::Number(n)),
                            span,
                        )),
                    ),
                    span: 1,
                }),
            ),
            span: 2,
        };
        assert_eq!(
            analyze_operand(parsed_expr, Context::Other),
            Ok(Operand::Deref(RelocExpr::from_atom(
                RelocAtom::Literal(n),
                span
            )))
        )
    }

    #[test]
    fn analyze_reg_in_expr() {
        let span = 0;
        let parsed_expr = SemanticExpr::<String, _> {
            variant: ExprVariant::Unary(
                SemanticUnary::Parentheses,
                Box::new(SemanticExpr {
                    variant: ExprVariant::Unary(
                        SemanticUnary::Parentheses,
                        Box::new(SemanticExpr::from_atom(
                            SemanticAtom::Literal(Literal::Operand(kw::Operand::Z)),
                            span,
                        )),
                    ),
                    span: 1,
                }),
            ),
            span: 2,
        };
        assert_eq!(
            analyze_operand(parsed_expr, Context::Other),
            Err(InternalDiagnostic::new(
                Message::KeywordInExpr { keyword: span },
                span
            ))
        )
    }

    #[test]
    fn analyze_string_in_instruction() {
        let span = 0;
        let parsed_expr = SemanticExpr::<String, _>::from_atom(
            SemanticAtom::Literal(Literal::String("some_string".into())),
            span,
        );
        assert_eq!(
            analyze_operand(parsed_expr, Context::Other),
            Err(InternalDiagnostic::new(Message::StringInInstruction, span))
        )
    }

    #[test]
    fn analyze_bare_hld() {
        test_bare_ptr_reg(kw::Operand::Hld)
    }

    #[test]
    fn analyze_bare_hli() {
        test_bare_ptr_reg(kw::Operand::Hli)
    }

    fn test_bare_ptr_reg(keyword: kw::Operand) {
        let span = 0;
        let expr = SemanticExpr::<String, _>::from_atom(
            SemanticAtom::Literal(Literal::Operand(keyword)),
            span,
        );
        assert_eq!(
            analyze_operand(expr, Context::Other),
            Err(InternalDiagnostic::new(
                Message::MustBeDeref { operand: span },
                span
            ))
        )
    }
}