cpclib-asm 0.6.0

cpclib libraries related to z80 assembling
Documentation
use std::fmt::Debug;

use cpclib_common::itertools::Itertools;
use cpclib_common::smallvec::SmallVec;
use cpclib_tokens::symbols::*;
use cpclib_tokens::tokens::*;

use crate::assembler::{assemble_defs_item, Env, Visited};
use crate::error::*;
use crate::implementation::expression::ExprEvaluationExt;
use crate::implementation::listing::ListingExt;
use crate::{AssemblingOptions, ParserContext};

/// Needed methods for the Token defined in cpclib_tokens
pub trait TokenExt: ListingElement + Clone + Debug {
    fn estimated_duration(&self) -> Result<usize, AssemblerError>;
    /// Unroll the tokens when it represents a loop
    fn unroll(&self, env: &crate::Env) -> Option<Result<Vec<&Self>, AssemblerError>>;

    /// Generate the listing of opcodes for directives that embed bytes
    fn disassemble_data(&self) -> Result<Listing, String>;

    fn to_bytes_with_options(&self, option: &AssemblingOptions) -> Result<Vec<u8>, AssemblerError>;

    fn number_of_bytes(&self) -> Result<usize, String> {
        let bytes = self.to_bytes();
        if bytes.is_ok() {
            Ok(bytes.ok().unwrap().len())
        }
        else {
            Err(format!("Unable to get the bytes of this token: {:?}", self))
        }
    }

    /// Return the number of bytes of the token given the provided context
    fn number_of_bytes_with_context(
        &self,
        table: &mut SymbolsTableCaseDependent
    ) -> Result<usize, String> {
        let bytes = self.to_bytes_with_context(table);
        if bytes.is_ok() {
            Ok(bytes.ok().unwrap().len())
        }
        else {
            eprintln!("{:?}", bytes);
            Err(format!("Unable to get the bytes of this token: {:?}", self))
        }
    }

    /// Dummy version that assemble without taking into account the context
    /// TODO find a way to not build a symbol table each time
    fn to_bytes(&self) -> Result<Vec<u8>, AssemblerError> {
        let mut table = SymbolsTableCaseDependent::laxist();
        let table = &mut table;
        self.to_bytes_with_context(table)
    }

    /// Assemble the symbol taking into account some context, but never modify this context
    #[allow(clippy::match_same_arms)]
    fn to_bytes_with_context(
        &self,
        table: &mut SymbolsTableCaseDependent
    ) -> Result<Vec<u8>, AssemblerError> {
        let mut options = if table.is_case_sensitive() {
            AssemblingOptions::new_case_sensitive()
        }
        else {
            AssemblingOptions::new_case_insensitive()
        };
        options.set_symbols(table.table());
        self.to_bytes_with_options(&options)
    }

    /// Check if the token is valid. We consider a token vlaid if it is possible to assemble it
    fn is_valid(&self) -> bool {
        self.to_bytes().is_ok()
    }
}

// impl<'t> TokenExt for Cow<'t, Token> {
// fn disassemble_data(&self) -> Result<Listing, String> {
// self.deref().disassemble_data()
// }
//
// fn estimated_duration(&self) -> Result<usize, AssemblerError> {
// self.deref().estimated_duration()
// }
//
// fn to_bytes_with_options(&self, option: &AssemblingOptions) -> Result<Vec<u8>, AssemblerError> {
// self.deref().to_bytes_with_options(option)
// }
//
// fn unroll(&self, _env: &crate::Env) -> Option<Result<Vec<&Self>, AssemblerError>> {
// unimplemented!("signature issue. should be transformed/unused")
// }
// }

impl TokenExt for Token {
    /// Unroll the tokens when in a repetition loop
    /// TODO return an iterator in order to not produce the vector each time
    fn unroll(&self, env: &crate::Env) -> Option<Result<Vec<&Self>, AssemblerError>> {
        if let Token::Repeat(ref expr, ref tokens, ref _counter_label, ref _counter_start) = self {
            let count: Result<ExprResult, AssemblerError> = expr.resolve(env);
            if count.is_err() {
                Some(Err(count.err().unwrap()))
            }
            else {
                let count = count.unwrap().int().unwrap();
                let mut res = Vec::with_capacity(count as usize * tokens.len());
                for _i in 0..count {
                    // TODO add a specific token to control the loop counter (and change the return type)
                    for t in tokens.iter() {
                        res.push(t);
                    }
                }
                Some(Ok(res))
            }
        }
        else {
            None
        }
    }

    /// Generate the listing of opcodes for directives that contain data Defb/defw/Defs in order to have
    /// mnemonics. Fails when some values are not opcodes
    fn disassemble_data(&self) -> Result<Listing, String> {
        // Disassemble the bytes and return the listing ONLY if it has no more defb/w/s directives
        let wrap = |bytes: &[u8]| {
            use crate::disass::disassemble;

            let lst = disassemble(&bytes);
            for token in lst.listing() {
                match token {
                    Token::Defb(_) | Token::Defw(_) | Token::Defs(_) => {
                        return Err(format!("{} as not been disassembled", token))
                    }
                    _ => {}
                }
            }

            return Ok(lst);
        };

        match self {
            Token::Defs(ref l) => {
                l.iter()
                    .map(|(e, f)| {
                        assemble_defs_item(e, f.as_ref(), &mut Env::default())
                            .or_else(|err| Err(format!("Unable to assemble {}: {:?}", self, err)))
                    })
                    .fold_ok(SmallVec::<[u8; 4]>::new(), |mut acc, v| {
                        acc.extend_from_slice(v.as_slice());
                        acc
                    })
                    .and_then(|b| wrap(&b))
            }

            Token::Defb(e) | Token::Defw(e) | Token::Str(e) => {
                use crate::assembler::visit_db_or_dw_or_str;

                let mut env = Env::default();
                visit_db_or_dw_or_str(self.into(), e, &mut env)
                    .map_err(|err| format!("Unable to assemble {}: {:?}", self, err))?;
                wrap(&env.produced_bytes())
            }

            _ => {
                let mut lst = Listing::new();
                lst.push(self.clone());
                Ok(lst)
            }
        }
    }

    fn to_bytes_with_options(&self, option: &AssemblingOptions) -> Result<Vec<u8>, AssemblerError> {
        let mut env = Env::new(option, &ParserContext::default());
        // we need several passes in case the token is a directive that contains code
        loop {
            env.start_new_pass();
            // println!("[pass] {:?}", env.pass);

            if env.pass().is_finished() {
                break;
            }

            self.visited(&mut env)?;
        }

        Ok(env.produced_bytes())
    }

    /// Returns an estimation of the duration.
    /// This estimation may be wrong for instruction having several states.
    #[allow(clippy::match_same_arms)]
    fn estimated_duration(&self) -> Result<usize, AssemblerError> {
        let duration = match self {
            Token::Assert(..)
            | Token::Breakpoint(_)
            | Token::Comment(_)
            | Token::Label(_)
            | Token::Equ(..)
            | Token::Protect(..) => 0,

            // Here, there is a strong limitation => it will works only if no symbols are used
            Token::Defw(_) | Token::Defb(_) | Token::Defs(_) => {
                self.disassemble_data()
                    .map_err(|e| AssemblerError::DisassemblerError { msg: e })
                    .and_then(|lst| lst.estimated_duration())?
            }

            Token::OpCode(ref mnemonic, ref arg1, ref arg2, ref _arg3) => {
                match mnemonic {
                    &Mnemonic::Add => {
                        match arg1 {
                            Some(DataAccess::Register8(_)) => {
                                match arg2 {
                                    Some(DataAccess::Register8(_)) => 1,
                                    Some(DataAccess::IndexRegister16WithIndex(..)) => 5,
                                    _ => 2
                                }
                            }
                            Some(DataAccess::Register16(_)) => 4,
                            Some(DataAccess::IndexRegister16(_)) => 5,
                            _ => panic!("Impossible case {:?}, {:?}, {:?}", mnemonic, arg1, arg2)
                        }
                    }

                    &Mnemonic::And | &Mnemonic::Or | &Mnemonic::Xor => {
                        match arg1 {
                            Some(DataAccess::Register8(_)) => 1,
                            Some(DataAccess::IndexRegister8(_)) => 2,
                            Some(DataAccess::Expression(_)) => 2,
                            Some(DataAccess::MemoryRegister16(_)) => 2,
                            Some(DataAccess::IndexRegister16WithIndex(..)) => 5,
                            _ => unreachable!()
                        }
                    }

                    Mnemonic::Call => {
                        match arg1 {
                            Some(_) => 3, // unstable (5 if jump)
                            None => 5
                        }
                    }

                    // XXX Not stable timing
                    &Mnemonic::Djnz => 3, // or 4

                    &Mnemonic::ExAf => 1,

                    &Mnemonic::Inc | &Mnemonic::Dec => {
                        match arg1 {
                            Some(DataAccess::Register8(_)) => 1,
                            Some(DataAccess::Register16(_)) => 2,
                            _ => panic!("Impossible case {:?}, {:?}, {:?}", mnemonic, arg1, arg2)
                        }
                    }

                    &Mnemonic::Jp => {
                        match arg1 {
                            &None => {
                                match arg2 {
                                    Some(DataAccess::Expression(_)) => 3,
                                    Some(DataAccess::MemoryRegister16(Register16::Hl)) => 1,
                                    Some(DataAccess::IndexRegister16(_)) => 2,
                                    _ => {
                                        panic!(
                                            "Impossible case {:?}, {:?}, {:?}",
                                            mnemonic, arg1, arg2
                                        )
                                    }
                                }
                            }

                            Some(DataAccess::FlagTest(_)) => {
                                match arg2 {
                                    Some(DataAccess::Expression(_)) => 3,
                                    _ => {
                                        panic!(
                                            "Impossible case {:?}, {:?}, {:?}",
                                            mnemonic, arg1, arg2
                                        )
                                    }
                                }
                            }

                            _ => panic!("Impossible case {:?}, {:?}, {:?}", mnemonic, arg1, arg2)
                        }
                    }

                    // Always give the fastest
                    &Mnemonic::Jr => {
                        match arg1 {
                            &None => {
                                match arg2 {
                                    Some(DataAccess::Expression(_)) => 3,
                                    _ => {
                                        panic!(
                                            "Impossible case {:?}, {:?}, {:?}",
                                            mnemonic, arg1, arg2
                                        )
                                    }
                                }
                            }

                            Some(DataAccess::FlagTest(_)) => {
                                match arg2 {
                                    Some(DataAccess::Expression(_)) => 2, // or 3
                                    _ => {
                                        panic!(
                                            "Impossible case {:?}, {:?}, {:?}",
                                            mnemonic, arg1, arg2
                                        )
                                    }
                                }
                            }

                            _ => panic!("Impossible case {:?}, {:?}, {:?}", mnemonic, arg1, arg2)
                        }
                    }

                    &Mnemonic::Ld => {
                        match arg1 {
                            // Dest in memory pointed by register
                            Some(DataAccess::MemoryRegister16(_)) => {
                                match arg2 {
                                    Some(DataAccess::Register8(_)) => 2,
                                    Some(DataAccess::Expression(_)) => 3, // XXX Valid only for HL
                                    _ => {
                                        panic!(
                                            "Impossible case {:?}, {:?}, {:?}",
                                            mnemonic, arg1, arg2
                                        )
                                    }
                                }
                            }

                            // Dest in 8bits reg
                            Some(DataAccess::Register8(ref _dst)) => {
                                match arg2 {
                                    Some(DataAccess::Register8(_)) => 1,
                                    Some(DataAccess::MemoryRegister16(Register16::Hl)) => 2,
                                    Some(DataAccess::Expression(_)) => 2,
                                    Some(DataAccess::Memory(_)) => 4,
                                    Some(DataAccess::IndexRegister16WithIndex(..)) => 5,
                                    _ => {
                                        panic!(
                                            "Impossible case {:?}, {:?}, {:?}",
                                            mnemonic, arg1, arg2
                                        )
                                    }
                                }
                            }

                            // Dest in 16bits reg
                            Some(DataAccess::Register16(ref dst)) => {
                                match arg2 {
                                    Some(DataAccess::Expression(_)) => 3,
                                    Some(DataAccess::Memory(_)) if dst == &Register16::Hl => 5,
                                    Some(DataAccess::Memory(_)) => 6,
                                    _ => {
                                        panic!(
                                            "Impossible case {:?}, {:?}, {:?}",
                                            mnemonic, arg1, arg2
                                        )
                                    }
                                }
                            }

                            Some(DataAccess::IndexRegister16(_)) => {
                                match arg2 {
                                    Some(DataAccess::Expression(_)) => 4,
                                    _ => {
                                        panic!(
                                            "Impossible case {:?}, {:?}, {:?}",
                                            mnemonic, arg1, arg2
                                        )
                                    }
                                }
                            }

                            Some(DataAccess::Memory(_)) => {
                                match arg2 {
                                    Some(DataAccess::Register8(Register8::A)) => 4,
                                    Some(DataAccess::Register16(Register16::Hl)) => 5,
                                    Some(DataAccess::Register16(_)) => 6,
                                    Some(DataAccess::IndexRegister16(_)) => 6,
                                    _ => {
                                        panic!(
                                            "Impossible case {:?}, {:?}, {:?}",
                                            mnemonic, arg1, arg2
                                        )
                                    }
                                }
                            }

                            _ => panic!("Impossible case {:?}, {:?}, {:?}", mnemonic, arg1, arg2)
                        }
                    }

                    &Mnemonic::Ldi | &Mnemonic::Ldd => 5,

                    &Mnemonic::Nop | &Mnemonic::Exx | &Mnemonic::Di | &Mnemonic::ExHlDe => 1,
                    &Mnemonic::Nop2 => 2,

                    &Mnemonic::Out => {
                        match arg1 {
                            Some(DataAccess::PortC) => 4, // XXX Not sure for out (c), 0
                            Some(DataAccess::Expression(_)) => 3,
                            _ => panic!("Impossible case {:?}, {:?}, {:?}", mnemonic, arg1, arg2)
                        }
                    }

                    Mnemonic::Outi | Mnemonic::Outd => 5,

                    &Mnemonic::Pop => {
                        match arg1 {
                            Some(DataAccess::Register16(_)) => 3,
                            Some(DataAccess::IndexRegister16(_)) => 4,
                            _ => panic!("Impossible case {:?}, {:?}, {:?}", mnemonic, arg1, arg2)
                        }
                    }

                    &Mnemonic::Push => {
                        match arg1 {
                            Some(DataAccess::Register16(_)) => 4,
                            Some(DataAccess::IndexRegister16(_)) => 5,
                            _ => panic!("Impossible case {:?}, {:?}, {:?}", mnemonic, arg1, arg2)
                        }
                    }

                    &Mnemonic::Res | &Mnemonic::Set => {
                        match arg2 {
                            Some(DataAccess::Register8(_)) => 2,
                            Some(DataAccess::MemoryRegister16(_)) => 3, // XXX only HL
                            Some(DataAccess::IndexRegister16WithIndex(..)) => 7,
                            _ => panic!("Impossible case {:?}, {:?}, {:?}", mnemonic, arg1, arg2)
                        }
                    }

                    &Mnemonic::Ret => {
                        match arg1 {
                            None => 3,
                            _ => panic!("Impossible case {:?}, {:?}, {:?}", mnemonic, arg1, arg2)
                        }
                    }

                    &Mnemonic::Sub => {
                        match arg1 {
                            Some(DataAccess::Register8(_)) => 1,
                            Some(DataAccess::IndexRegister8(_)) => 2,
                            Some(DataAccess::Expression(_)) => 2,
                            Some(DataAccess::MemoryRegister16(Register16::Hl)) => 2,
                            Some(DataAccess::IndexRegister16WithIndex(..)) => 5,
                            _ => panic!("Impossible case {:?}, {:?}, {:?}", mnemonic, arg1, arg2)
                        }
                    }

                    _ => {
                        panic!(
                            "Duration not set for {:?}, {:?}, {:?}",
                            mnemonic, arg1, arg2
                        )
                    }
                }
            }
            _ => {
                return Err(AssemblerError::BugInAssembler {
                    msg: format!("Duration computation for {:?} not yet coded", self)
                })
            }
        };
        Ok(duration)
    }
}

#[cfg(test)]
#[allow(clippy::pedantic)]
#[allow(warnings)]
mod tests {
    use crate::preamble::*;

    #[test]
    fn test_timing2() {
        // We are only able to disassemble nop ...
        assert_eq!(defs_expr_expr(10, 0).estimated_duration().unwrap(), 10);
        assert_eq!(defw(0).estimated_duration().unwrap(), 2);
        assert_eq!(defb(0).estimated_duration().unwrap(), 1);

        assert_eq!(exx().estimated_duration().unwrap(), 1);

        assert_eq!(pop_de().estimated_duration().unwrap(), 3);

        assert_eq!(inc_l().estimated_duration().unwrap(), 1);

        assert_eq!(jp_label("XX").estimated_duration().unwrap(), 3);

        assert_eq!(ld_l_mem_ix(14.into()).estimated_duration().unwrap(), 5);

        assert_eq!(ld_mem_hl_e().estimated_duration().unwrap(), 2);

        assert_eq!(ld_e_mem_hl().estimated_duration().unwrap(), 2);

        assert_eq!(ld_d_mem_hl().estimated_duration().unwrap(), 2);

        assert_eq!(out_c_d().estimated_duration().unwrap(), 4);
    }

    #[test]
    fn is_valid_ok() {
        assert!(out_c_d().is_valid());
    }

    #[test]
    fn is_valid_nok() {
        assert!(!Token::OpCode(
            Mnemonic::Out,
            Some(DataAccess::Register8(Register8::C)),
            Some(DataAccess::Register8(Register8::A)),
            None
        )
        .is_valid());
    }

    #[cfg(test)]
    mod test {

        use ParseToken;

        use super::*;
        #[test]
        fn fixup_duration() {
            assert_eq!(
                Token::parse_token(" di")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                1
            );
            assert_eq!(
                Token::parse_token(" add a,c ")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                1
            );
            assert_eq!(
                Token::parse_token(" ld l, a")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                1
            );
            assert_eq!(
                Token::parse_token(" ld b, e")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                1
            );
            assert_eq!(
                Token::parse_token(" ld e, b")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                1
            );
            assert_eq!(
                Token::parse_token(" exx")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                1
            );
            assert_eq!(
                Token::parse_token(" push bc")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                4
            );
            assert_eq!(
                Token::parse_token(" pop bc")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                3
            );
            assert_eq!(
                Token::parse_token(" push ix")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                5
            );
            assert_eq!(
                Token::parse_token(" pop ix")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                4
            );
            assert_eq!(
                Token::parse_token(" ld b, nnn")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                2
            );
            assert_eq!(
                Token::parse_token(" ld e, (hl)")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                2
            );
            assert_eq!(
                Token::parse_token(" ld d, (hl)")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                2
            );
            assert_eq!(
                Token::parse_token(" ld a, (hl)")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                2
            );
            assert_eq!(
                Token::parse_token(" ld a, (dd)")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                4
            );
            assert_eq!(
                Token::parse_token(" ld hl, (dd)")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                5
            );
            println!("{:?}", Token::parse_token(" ld de, (dd)").unwrap());
            assert_eq!(
                Token::parse_token(" ld de, (dd)")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                6
            );
            assert_eq!(
                Token::parse_token(" ld a, (ix+0)")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                5
            );
            assert_eq!(
                Token::parse_token(" ld l, (ix+0)")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                5
            );
            assert_eq!(
                Token::parse_token(" ldi")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                5
            );
            assert_eq!(
                Token::parse_token(" inc c")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                1
            );
            assert_eq!(
                Token::parse_token(" inc l")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                1
            );
            assert_eq!(
                Token::parse_token(" dec c")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                1
            );
            assert_eq!(
                Token::parse_token(" out (c), d")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                4
            );
            assert_eq!(
                Token::parse_token(" out (c), c")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                4
            );
            assert_eq!(
                Token::parse_token(" out (c), e")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                4
            );
            assert_eq!(
                Token::parse_token(" ld b, 0x7f")
                    .unwrap()
                    .estimated_duration()
                    .unwrap(),
                2
            );

            assert!(Token::Basic(None, None, "".to_owned())
                .estimated_duration()
                .is_err());
        }
    }
}