pcode 0.1.3

Pure Rust implementation of a p-code disassembler and lifter.
Documentation
use super::opcode::OpCode;
use super::seqnum::SeqNum;

use std::fmt::{self, Display, Debug};
use std::hash::Hash;

#[derive(Eq, PartialEq, Hash, Clone)]
pub struct Op<T> {
    pub seq: SeqNum,
    pub opcode: OpCode,
    pub inputs: Vec<T>,
    pub output: Option<T>,
}

pub trait FmtIface: Display {
    fn fmt_output(&self) -> String;
    fn fmt_call_target(&self) -> String;
}

pub fn parenthesize(s: String) -> String {
    let trim_s = s.trim_start_matches('*');
    let first_char = trim_s.chars().next().unwrap();
    let last_char = trim_s.chars().last().unwrap();

    if trim_s.contains(' ') && (first_char != '(' || last_char != ')') {
        format!("({})", s)
    } else {
        s
    }
}

fn fmt_unary<T: Display>(opstr: &str, inputs: &[T]) -> String {
    format!("{}({})", opstr, inputs[0])
}

fn fmt_binary<T: Display>(opstr: &str, inputs: &[T]) -> String {
    let lhs_str = parenthesize(inputs[0].to_string());
    let rhs_str = parenthesize(inputs[1].to_string());
    format!("{} {} {}", lhs_str, opstr, rhs_str)
}

fn fmt_func<T: Display>(opstr: &str, inputs: &[T]) -> String {
    format!("{}({})", opstr, inputs.iter()
        .map(|x| format!("{}", x))
        .collect::<Vec<String>>()
        .join(", "))
}

pub fn fmt_inputs<T: FmtIface>(opcode: OpCode, inputs: &[T], size: u64) -> String {
    match opcode {
        OpCode::Copy => inputs[0].to_string(),
        OpCode::Decl => inputs[0].fmt_output(),
        OpCode::DeclCopy => inputs[0].to_string(),
        OpCode::Store => format!("*{} = {}", parenthesize(inputs[0].to_string()), inputs[1]),
        OpCode::Load => format!("*{}", parenthesize(inputs[0].to_string())),
        OpCode::Deref => format!("*{}:{}", parenthesize(inputs[0].to_string()), size),
        OpCode::FieldAccess => format!("{}.{}", parenthesize(inputs[0].to_string()), inputs[1]),
        OpCode::FieldDeref => format!("{}->{}", parenthesize(inputs[0].to_string()), inputs[1]),
        OpCode::ArrayIndex => format!("{}[{}]", parenthesize(inputs[0].to_string()), inputs[1]),
        OpCode::AddrOf => format!("&{}", inputs[0]),
        OpCode::Branch => format!("\x1b[38;5;105mgoto\x1b[0m {}", inputs[0]),
        OpCode::BranchInd => format!("\x1b[38;5;105mgoto\x1b[0m [{}]", inputs[0]),
        OpCode::Call => format!("{}({})", inputs[0].fmt_call_target(), inputs[1..].iter()
            .map(|x| format!("{}", x))
            .collect::<Vec<String>>()
            .join(", ")),
        OpCode::CallInd => {
            let mut input_iter = inputs.iter();
            let _ = input_iter.next();

            format!("*({})({})", inputs[0], 
                input_iter
                .map(|x| format!("{}", x))
                .collect::<Vec<String>>()
                .join(", "))
        },
        OpCode::CBranch => format!("\x1b[38;5;105mif\x1b[0m ({}) \x1b[38;5;105mgoto\x1b[0m {}", inputs[1], inputs[0]),
        OpCode::Return => format!("\x1b[38;5;105mreturn\x1b[0m {}", inputs.iter()
            .map(|x| format!("{}", x))
            .collect::<Vec<String>>()
            .join(", ")),
        OpCode::MultiEqual => fmt_func("phi", inputs),
        OpCode::CPoolRef => fmt_func("cpool", inputs),
        OpCode::FloatAbs => fmt_func("abs", inputs),
        OpCode::FloatCeil => fmt_func("ceil", inputs),
        OpCode::FloatFloat2Float => fmt_func("float2float", inputs),
        OpCode::FloatFloor => fmt_func("floor", inputs),
        OpCode::FloatInt2Float => fmt_func("float", inputs),
        OpCode::FloatNan => fmt_func("nan", inputs),
        OpCode::FloatRound => fmt_func("round", inputs),
        OpCode::FloatSqrt => fmt_func("sqrt", inputs),
        OpCode::FloatTrunc => fmt_func("trunc", inputs),
        OpCode::IntCarry => fmt_func("carry", inputs),
        OpCode::IntSBorrow => fmt_func("sborrow", inputs),
        OpCode::IntSCarry => fmt_func("scarry", inputs),
        OpCode::IntSext => fmt_func("sext", inputs),
        OpCode::IntZext => fmt_func("zext", inputs),
        OpCode::New => fmt_func("newobject", inputs),
        OpCode::PopCount => fmt_func("popcount", inputs),
        OpCode::BoolAnd => fmt_binary("&&", inputs),
        OpCode::BoolOr => fmt_binary("||", inputs),
        OpCode::BoolXor => fmt_binary("^^", inputs),
        OpCode::FloatAdd => fmt_binary("f+", inputs),
        OpCode::FloatDiv => fmt_binary("f/", inputs),
        OpCode::FloatEqual => fmt_binary("f==", inputs),
        OpCode::FloatLess => fmt_binary("f<", inputs),
        OpCode::FloatLessEqual => fmt_binary("f<=", inputs),
        OpCode::FloatMult => fmt_binary("f*", inputs),
        OpCode::FloatNotEqual => fmt_binary("f!=", inputs),
        OpCode::FloatSub => fmt_binary("f-", inputs),
        OpCode::IntAdd => fmt_binary("+", inputs),
        OpCode::IntAnd => fmt_binary("&", inputs),
        OpCode::IntDiv => fmt_binary("/", inputs),
        OpCode::IntEqual => fmt_binary("==", inputs),
        OpCode::IntLess => fmt_binary("<", inputs),
        OpCode::IntGreater => fmt_binary(">", inputs),
        OpCode::IntLeft => fmt_binary("<<", inputs),
        OpCode::IntLessEqual => fmt_binary("<=", inputs),
        OpCode::IntGreaterEqual => fmt_binary(">=", inputs),
        OpCode::IntMult => fmt_binary("*", inputs),
        OpCode::IntNegate => fmt_unary("~", inputs),
        OpCode::IntNotEqual => fmt_binary("!=", inputs),
        OpCode::IntOr => fmt_binary("|", inputs),
        OpCode::IntRem => fmt_binary("%", inputs),
        OpCode::IntRight => fmt_binary(">>", inputs),
        OpCode::IntSDiv => fmt_binary("s/", inputs),
        OpCode::IntSLess => fmt_binary("s<", inputs),
        OpCode::IntSLessEqual => fmt_binary("s<=", inputs),
        OpCode::IntSGreater => fmt_binary("s>", inputs),
        OpCode::IntSGreaterEqual => fmt_binary("s>=", inputs),
        OpCode::IntSRem => fmt_binary("s%", inputs),
        OpCode::IntSRight => fmt_binary("s>>", inputs),
        OpCode::IntSub => fmt_binary("-", inputs),
        OpCode::IntXor => fmt_binary("^", inputs),
        OpCode::BoolNegate => fmt_unary("!", inputs),
        OpCode::FloatNeg => fmt_unary("f-", inputs),
        OpCode::Int2Comp => fmt_unary("-", inputs),
        OpCode::CallOther => fmt_func("callother", inputs),
        OpCode::SubPiece => format!("({})({})", inputs[0], inputs[1]),
        OpCode::Truncate => format!("{}:{}", parenthesize(inputs[0].to_string()), inputs[1]),
        OpCode::Switch => format!("\x1b[38;5;105mswitch\x1b[0m ({})", inputs[0]),
        _ => panic!("Don't know how to format {}!", opcode)
    }
}

fn fmt_output<T: FmtIface>(opcode: OpCode, output: Option<&T>) -> String {
    match output {
        Some(vnode) => if opcode == OpCode::DeclCopy {
            format!("{} = ", vnode.fmt_output())
        } else {
            format!("{} = ", vnode)
        },
        None        => "".to_string()
    }
}

impl<T: FmtIface> Display for Op<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{}{}",
            fmt_output(self.opcode, self.output.as_ref()),
            fmt_inputs(self.opcode, &self.inputs, 0)
        )
    }
}

impl<T: FmtIface> Debug for Op<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self)
    }
}

macro_rules! op_matches {
    ($op:expr, ($output:expr => $opcode:ident $($input:expr),*)) => {{
        $op.output
            .as_ref()
            .map(|out|
                out == $output && op_matches!($op, ($opcode $($input),*))
            ).unwrap_or(false)
    }};

    ($op:expr, ($opcode:ident $lhs:expr, $rhs:expr)) => {{
        $op.opcode == OpCode::$opcode &&
            $op.inputs.len() == 2 &&
            &$op.inputs[0] == $lhs &&
            &$op.inputs[1] == $rhs
    }};

    ($op:expr, ($opcode:ident $operand:expr)) => {{
        $op.opcode == OpCode::$opcode &&
            $op.inputs.len() == 1 &&
            &$op.inputs[0] == $operand
    }};
}

pub(crate) use op_matches;