xo65 0.1.0

Library for parsing cc65 object files (xo65 format)
Documentation
use crate::expr::Expr;
use crate::parse::{
    parse_bytes_at, parse_uleb128_at, ParseAt, ParseErrorContext as _, ParseResult,
};
use crate::xo65::Xo65Bytes;

#[derive(Debug)]
pub struct ExportTable<'data> {
    bytes: Xo65Bytes<'data>,

    exports: Box<[Export]>,
}

impl<'data> ExportTable<'data> {
    pub fn as_bytes(&self) -> &'data [u8] {
        self.bytes.as_inner()
    }

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

    pub fn get(&self, idx: usize) -> Option<&Export> {
        self.exports.get(idx)
    }

    pub fn iter(
        &self,
    ) -> impl DoubleEndedIterator<Item = &Export> + ExactSizeIterator + std::iter::FusedIterator + Clone
    {
        self.exports.iter()
    }

    pub(crate) fn parse(bytes: Xo65Bytes<'data>) -> ParseResult<Self> {
        let mut off = 0;

        let count = parse_uleb128_at(&bytes, &mut off)? as usize;
        let mut exports = Vec::<Export>::with_capacity(count);

        for i in 0..count {
            let export = Export::parse_at(&bytes, &mut off)
                .with_context(|| format!("export {i} parse error"))?;
            exports.push(export);
        }

        Ok(Self {
            bytes,
            exports: exports.into(),
        })
    }
}

/// エクスポートシンボル。
#[derive(Debug)]
pub struct Export {
    name: u32,
    info: ExportInfo,
    addr_size: u8,
    size: Option<u32>,
    expr: Expr,
    attrs: Box<[ExportAttr]>,
    def_lines: Box<[u32]>,
    ref_lines: Box<[u32]>,
}

impl Export {
    pub fn name(&self) -> u32 {
        self.name
    }

    pub fn info(&self) -> ExportInfo {
        self.info
    }

    pub fn addr_size(&self) -> u8 {
        self.addr_size
    }

    pub fn size(&self) -> Option<u32> {
        self.size
    }

    pub fn expr(&self) -> &Expr {
        &self.expr
    }

    pub fn attrs(&self) -> &[ExportAttr] {
        &self.attrs
    }

    pub fn def_lines(&self) -> &[u32] {
        &self.def_lines
    }

    pub fn ref_lines(&self) -> &[u32] {
        &self.ref_lines
    }
}

impl ParseAt<'_> for Export {
    fn parse_at(bytes: &Xo65Bytes<'_>, off: &mut usize) -> ParseResult<Self> {
        let info = parse_uleb128_at(bytes, off).map(ExportInfo)?;
        // TODO: info の bit7 (SYM_EXPORT) が立っていないとき警告する?
        let addr_size = u8::parse_at(bytes, off)?;
        let attrs = parse_attrs_at(bytes, off, info.attr_count())?;
        let name = parse_uleb128_at(bytes, off)?;
        let expr = if info.is_const() {
            let value = i32::parse_at(bytes, off)?;
            Expr::literal(i64::from(value))
        } else {
            Expr::parse_at(bytes, off)?
        };
        let size = info
            .has_size()
            .then(|| parse_uleb128_at(bytes, off))
            .transpose()?;
        let def_lines = parse_lines_at(bytes, off)?;
        let ref_lines = parse_lines_at(bytes, off)?;

        Ok(Self {
            name,
            info,
            addr_size,
            size,
            expr,
            attrs,
            def_lines,
            ref_lines,
        })
    }
}

fn parse_attrs_at(
    bytes: &Xo65Bytes<'_>,
    off: &mut usize,
    count: u8,
) -> ParseResult<Box<[ExportAttr]>> {
    let count = usize::from(count);

    let ary = parse_bytes_at(bytes, off, count)?;
    let ary: Box<_> = ary.iter().copied().map(ExportAttr).collect();

    Ok(ary)
}

fn parse_lines_at(bytes: &Xo65Bytes<'_>, off: &mut usize) -> ParseResult<Box<[u32]>> {
    let count = parse_uleb128_at(bytes, off)? as usize;
    let mut lines = Vec::<u32>::with_capacity(count);

    for _ in 0..count {
        let line = parse_uleb128_at(bytes, off)?;
        lines.push(line);
    }

    Ok(lines.into())
}

/// エクスポートシンボルに関する情報。
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ExportInfo(u32);

impl ExportInfo {
    pub fn get(self) -> u32 {
        self.0
    }

    pub fn attr_count(self) -> u8 {
        (self.0 & 0x7) as u8
    }

    pub fn has_size(self) -> bool {
        (self.0 & (1 << 3)) != 0
    }

    pub fn is_const(self) -> bool {
        (self.0 & (1 << 4)) == 0
    }

    pub fn is_expr(self) -> bool {
        !self.is_const()
    }

    pub fn is_label(self) -> bool {
        (self.0 & (1 << 5)) != 0
    }

    pub fn is_cheap_local(self) -> bool {
        (self.0 & (1 << 6)) != 0
    }
}

/// エクスポートシンボルの特殊な属性。`.condes` コマンドなどにより付与される。
///
/// cc65 では "ConDes" と呼ばれている。
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ExportAttr(u8);

impl ExportAttr {
    pub fn get(self) -> u8 {
        self.0
    }

    pub fn kind(self) -> ExportAttrKind {
        ExportAttrKind::new(self.0 >> 5)
    }

    pub fn priority(self) -> u8 {
        self.0 & 0x1F
    }
}

/// エクスポートシンボルの特殊な属性の種類。
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ExportAttrKind {
    Constructor = 0,
    Destructor = 1,
    Interruptor = 2,
    Kind3 = 3,
    Kind4 = 4,
    Kind5 = 5,
    Kind6 = 6,
    Kind7 = 7,
}

impl ExportAttrKind {
    fn new(inner: u8) -> Self {
        match inner {
            0 => Self::Constructor,
            1 => Self::Destructor,
            2 => Self::Interruptor,
            3 => Self::Kind3,
            4 => Self::Kind4,
            5 => Self::Kind5,
            6 => Self::Kind6,
            7 => Self::Kind7,
            _ => unreachable!(),
        }
    }
}