gbas 0.1.0

An assembler for Game Boy
Documentation
use super::{Analysis, AnalysisResult, AtomKind, Operand, SimpleOperand};
use diagnostics::{InternalDiagnostic, Message};
use instruction::{Branch, Condition, Instruction, Nullary, RelocExpr};
use span::{Source, Span};

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum BranchKind {
    Explicit(ExplicitBranch),
    Implicit(ImplicitBranch),
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ExplicitBranch {
    Call,
    Jp,
    Jr,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ImplicitBranch {
    Ret,
    Reti,
}

impl<'a, S: Span, I: Iterator<Item = Result<Operand<S>, InternalDiagnostic<S>>>> Analysis<S, I> {
    pub fn analyze_branch(&mut self, branch: BranchKind) -> AnalysisResult<S> {
        let (condition, target) = self.collect_branch_operands()?;
        let variant = analyze_branch_variant((branch, &self.mnemonic.1), target)?;
        match variant {
            BranchVariant::Unconditional(branch) => match condition {
                None => Ok(branch.into()),
                Some((_, condition_span)) => Err(InternalDiagnostic::new(
                    Message::AlwaysUnconditional,
                    condition_span,
                )),
            },
            BranchVariant::PotentiallyConditional(branch) => Ok(Instruction::Branch(
                branch,
                condition.map(|(condition, _)| condition),
            )),
        }
    }

    fn collect_branch_operands(&mut self) -> Result<BranchOperands<S>, InternalDiagnostic<S>> {
        let first_operand = self.operands.next()?;
        Ok(
            if let Some(Operand::Atom(AtomKind::Condition(condition), range)) = first_operand {
                (
                    Some((condition, range)),
                    analyze_branch_target(self.operands.next()?)?,
                )
            } else {
                (None, analyze_branch_target(first_operand)?)
            },
        )
    }
}

type BranchOperands<S> = (Option<(Condition, S)>, Option<BranchTarget<S>>);

enum BranchTarget<S> {
    DerefHl(S),
    Expr(RelocExpr<S>),
}

impl<S: Span> Source for BranchTarget<S> {
    type Span = S;
    fn span(&self) -> Self::Span {
        match self {
            BranchTarget::DerefHl(span) => span.clone(),
            BranchTarget::Expr(expr) => expr.span(),
        }
    }
}

fn analyze_branch_target<S: Span>(
    target: Option<Operand<S>>,
) -> Result<Option<BranchTarget<S>>, InternalDiagnostic<S>> {
    let target = match target {
        Some(target) => target,
        None => return Ok(None),
    };
    match target {
        Operand::Const(expr) => Ok(Some(BranchTarget::Expr(expr))),
        Operand::Atom(AtomKind::Simple(SimpleOperand::DerefHl), span) => {
            Ok(Some(BranchTarget::DerefHl(span)))
        }
        operand => Err(InternalDiagnostic::new(
            Message::CannotBeUsedAsTarget,
            operand.span(),
        )),
    }
}

enum BranchVariant<S> {
    PotentiallyConditional(Branch<S>),
    Unconditional(UnconditionalBranch),
}

enum UnconditionalBranch {
    JpDerefHl,
    Reti,
}

impl<S> From<UnconditionalBranch> for Instruction<S> {
    fn from(branch: UnconditionalBranch) -> Self {
        match branch {
            UnconditionalBranch::JpDerefHl => Instruction::JpDerefHl,
            UnconditionalBranch::Reti => Instruction::Nullary(Nullary::Reti),
        }
    }
}

fn analyze_branch_variant<S: Span>(
    kind: (BranchKind, &S),
    target: Option<BranchTarget<S>>,
) -> Result<BranchVariant<S>, InternalDiagnostic<S>> {
    match (kind.0, target) {
        (BranchKind::Explicit(ExplicitBranch::Jp), Some(BranchTarget::DerefHl(_))) => {
            Ok(BranchVariant::Unconditional(UnconditionalBranch::JpDerefHl))
        }
        (BranchKind::Explicit(_), Some(BranchTarget::DerefHl(span))) => {
            Err(InternalDiagnostic::new(
                Message::RequiresConstantTarget {
                    mnemonic: kind.1.clone(),
                },
                span,
            ))
        }
        (BranchKind::Explicit(branch), Some(BranchTarget::Expr(expr))) => Ok(
            BranchVariant::PotentiallyConditional(mk_explicit_branch(branch, expr)),
        ),
        (BranchKind::Implicit(ImplicitBranch::Ret), None) => {
            Ok(BranchVariant::PotentiallyConditional(Branch::Ret))
        }
        (BranchKind::Implicit(ImplicitBranch::Reti), None) => {
            Ok(BranchVariant::Unconditional(UnconditionalBranch::Reti))
        }
        (BranchKind::Explicit(_), None) => Err(InternalDiagnostic::new(
            Message::MissingTarget,
            kind.1.clone(),
        )),
        (BranchKind::Implicit(_), Some(target)) => Err(InternalDiagnostic::new(
            Message::CannotSpecifyTarget,
            target.span(),
        )),
    }
}

fn mk_explicit_branch<S>(branch: ExplicitBranch, target: RelocExpr<S>) -> Branch<S> {
    match branch {
        ExplicitBranch::Call => Branch::Call(target),
        ExplicitBranch::Jp => Branch::Jp(target),
        ExplicitBranch::Jr => Branch::Jr(target),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use expr::Expr;
    use frontend::semantics::instruction::tests::*;
    use frontend::syntax::keyword::Mnemonic;

    #[test]
    fn analyze_legal_branch_instructions() {
        test_instruction_analysis(describe_branch_instuctions())
    }

    #[derive(Clone, Copy, PartialEq)]
    enum PotentiallyConditionalBranch {
        Explicit(ExplicitBranch),
        Ret,
    }

    impl From<PotentiallyConditionalBranch> for Mnemonic {
        fn from(branch: PotentiallyConditionalBranch) -> Self {
            use self::{ExplicitBranch::*, PotentiallyConditionalBranch::*};
            match branch {
                Explicit(Call) => Mnemonic::Call,
                Explicit(Jp) => Mnemonic::Jp,
                Explicit(Jr) => Mnemonic::Jr,
                Ret => Mnemonic::Ret,
            }
        }
    }

    fn describe_branch_instuctions() -> Vec<InstructionDescriptor> {
        use self::{ExplicitBranch::*, PotentiallyConditionalBranch::*};
        let mut descriptors = vec![
            (
                (Mnemonic::Jp, vec![deref(literal(Hl))]),
                Instruction::JpDerefHl,
            ),
            (
                (Mnemonic::Reti, vec![]),
                Instruction::Nullary(Nullary::Reti),
            ),
        ];
        for &kind in [Explicit(Call), Explicit(Jp), Explicit(Jr), Ret].iter() {
            descriptors.push(describe_branch(kind, None));
            for &condition in &[Condition::C, Condition::Nc, Condition::Nz, Condition::Z] {
                descriptors.push(describe_branch(kind, Some(condition)))
            }
        }
        descriptors
    }

    fn describe_branch(
        branch: PotentiallyConditionalBranch,
        condition: Option<Condition>,
    ) -> InstructionDescriptor {
        use self::PotentiallyConditionalBranch::*;
        let ident = "ident";
        let mut operands = Vec::new();
        let mut has_condition = false;
        if let Some(condition) = condition {
            operands.push(Expr::from(condition));
            has_condition = true;
        };
        if branch != Ret {
            operands.push(ident.into());
        };
        (
            (Mnemonic::from(branch), operands),
            Instruction::Branch(
                match branch {
                    Ret => Branch::Ret,
                    Explicit(explicit) => mk_explicit_branch(
                        explicit,
                        symbol(
                            ident,
                            TokenId::Operand(if has_condition { 1 } else { 0 }, 0),
                        ),
                    ),
                },
                condition,
            ),
        )
    }

    #[test]
    fn analyze_jp_c_deref_hl() {
        analyze(
            Mnemonic::Jp,
            vec![literal(C), SimpleOperand::DerefHl.into()],
        ).expect_diagnostic(
            ExpectedDiagnostic::new(Message::AlwaysUnconditional)
                .with_highlight(TokenId::Operand(0, 0)),
        )
    }

    #[test]
    fn analyze_jp_z() {
        analyze(Mnemonic::Jp, vec![literal(Z)]).expect_diagnostic(
            ExpectedDiagnostic::new(Message::MissingTarget).with_highlight(TokenId::Mnemonic),
        )
    }

    #[test]
    fn analyze_ret_a() {
        analyze(Mnemonic::Ret, vec![literal(A)]).expect_diagnostic(
            ExpectedDiagnostic::new(Message::CannotBeUsedAsTarget)
                .with_highlight(TokenId::Operand(0, 0)),
        )
    }

    #[test]
    fn analyze_reti_z() {
        analyze(Mnemonic::Reti, vec![literal(Z)]).expect_diagnostic(
            ExpectedDiagnostic::new(Message::AlwaysUnconditional)
                .with_highlight(TokenId::Operand(0, 0)),
        )
    }

    #[test]
    fn analyze_ret_z_ident() {
        analyze(Mnemonic::Ret, vec![literal(Z), "target".into()]).expect_diagnostic(
            ExpectedDiagnostic::new(Message::CannotSpecifyTarget)
                .with_highlight(TokenId::Operand(1, 0)),
        )
    }

    #[test]
    fn analyze_call_deref_hl() {
        analyze(Mnemonic::Call, vec![deref(literal(Hl))]).expect_diagnostic(
            ExpectedDiagnostic::new(Message::RequiresConstantTarget {
                mnemonic: TokenId::Mnemonic.into(),
            }).with_highlight(
                TokenSpan::from(TokenId::Operand(0, 0)).extend(&TokenId::Operand(0, 2).into()),
            ),
        )
    }
}