use crate::{
error::Error,
operand::{Operand, parse_operand},
reader::Reader,
ty::Type,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum CalcOp {
Add,
Sub,
Mul,
Div,
Mod,
Shl,
Shr,
And,
Or,
Xor,
}
impl CalcOp {
fn from_byte(b: u8) -> Result<Self, Error> {
match b {
0 => Ok(Self::Add),
1 => Ok(Self::Sub),
2 => Ok(Self::Mul),
3 => Ok(Self::Div),
4 => Ok(Self::Mod),
5 => Ok(Self::Shl),
6 => Ok(Self::Shr),
7 => Ok(Self::And),
8 => Ok(Self::Or),
9 => Ok(Self::Xor),
other => Err(Error::UnknownBaseType { byte: other }),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum CompareOp {
GreaterEq,
LessEq,
Greater,
Less,
NotEqual,
Equal,
}
impl CompareOp {
fn from_byte(b: u8) -> Result<Self, Error> {
match b {
0 => Ok(Self::GreaterEq),
1 => Ok(Self::LessEq),
2 => Ok(Self::Greater),
3 => Ok(Self::Less),
4 => Ok(Self::NotEqual),
5 => Ok(Self::Equal),
other => Err(Error::UnknownBaseType { byte: other }),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum ExceptionHandlerEnd {
EndOfBlock,
EndOfFirstFinally,
EndOfExcept,
EndOfSecondFinally,
}
impl ExceptionHandlerEnd {
fn from_byte(b: u8) -> Result<Self, Error> {
match b {
0 => Ok(Self::EndOfBlock),
1 => Ok(Self::EndOfFirstFinally),
2 => Ok(Self::EndOfExcept),
3 => Ok(Self::EndOfSecondFinally),
other => Err(Error::UnknownBaseType { byte: other }),
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum Opcode<'a> {
Assign { dest: Operand<'a>, src: Operand<'a> },
CalculateAssign {
op: CalcOp,
dest: Operand<'a>,
src: Operand<'a>,
},
Push { var: Operand<'a> },
PushVar { var: Operand<'a> },
Pop,
Call { proc_no: u32 },
Goto { offset: i32 },
CondGoto { offset: u32, cond: Operand<'a> },
CondNotGoto { offset: u32, cond: Operand<'a> },
Return,
SetStackType {
new_type: u32,
offset_from_base: u32,
},
PushType { type_no: u32 },
Compare {
op: CompareOp,
into: Operand<'a>,
lhs: Operand<'a>,
rhs: Operand<'a>,
},
CallVar { var: Operand<'a> },
SetPointer { dest: Operand<'a>, src: Operand<'a> },
BoolNot { var: Operand<'a> },
Negate { var: Operand<'a> },
SetFlag { var: Operand<'a>, invert: bool },
FlagGoto { target: u32 },
PushExceptionHandler {
finally_offset: u32,
exception_offset: u32,
finally2_offset: u32,
end_of_block: u32,
},
PopExceptionHandler { position: ExceptionHandlerEnd },
IntegerNot { var: Operand<'a> },
SetStackPointerToCopy { target: u32 },
Inc { var: Operand<'a> },
Dec { var: Operand<'a> },
PopAndGoto { offset: i32 },
Pop2AndGoto { offset: i32 },
Nop,
}
impl Opcode<'_> {
pub fn raw_byte(&self) -> u8 {
match self {
Self::Assign { .. } => 0,
Self::CalculateAssign { .. } => 1,
Self::Push { .. } => 2,
Self::PushVar { .. } => 3,
Self::Pop => 4,
Self::Call { .. } => 5,
Self::Goto { .. } => 6,
Self::CondGoto { .. } => 7,
Self::CondNotGoto { .. } => 8,
Self::Return => 9,
Self::SetStackType { .. } => 10,
Self::PushType { .. } => 11,
Self::Compare { .. } => 12,
Self::CallVar { .. } => 13,
Self::SetPointer { .. } => 14,
Self::BoolNot { .. } => 15,
Self::Negate { .. } => 16,
Self::SetFlag { .. } => 17,
Self::FlagGoto { .. } => 18,
Self::PushExceptionHandler { .. } => 19,
Self::PopExceptionHandler { .. } => 20,
Self::IntegerNot { .. } => 21,
Self::SetStackPointerToCopy { .. } => 22,
Self::Inc { .. } => 23,
Self::Dec { .. } => 24,
Self::PopAndGoto { .. } => 25,
Self::Pop2AndGoto { .. } => 26,
Self::Nop => 255,
}
}
}
pub(crate) fn parse_opcode<'a>(
reader: &mut Reader<'a>,
types: &[Type<'a>],
) -> Result<Opcode<'a>, Error> {
let raw = reader.u8("opcode byte")?;
let op = match raw {
0 => Opcode::Assign {
dest: parse_operand(reader, types)?,
src: parse_operand(reader, types)?,
},
1 => {
let calc = CalcOp::from_byte(reader.u8("CalcType")?)?;
let dest = parse_operand(reader, types)?;
let src = parse_operand(reader, types)?;
Opcode::CalculateAssign {
op: calc,
dest,
src,
}
}
2 => Opcode::Push {
var: parse_operand(reader, types)?,
},
3 => Opcode::PushVar {
var: parse_operand(reader, types)?,
},
4 => Opcode::Pop,
5 => Opcode::Call {
proc_no: reader.u32_le("Call ProcNo")?,
},
6 => Opcode::Goto {
offset: reader.i32_le("Goto offset")?,
},
7 => Opcode::CondGoto {
offset: reader.u32_le("CondGoto offset")?,
cond: parse_operand(reader, types)?,
},
8 => Opcode::CondNotGoto {
offset: reader.u32_le("CondNotGoto offset")?,
cond: parse_operand(reader, types)?,
},
9 => Opcode::Return,
10 => Opcode::SetStackType {
new_type: reader.u32_le("SetStackType NewType")?,
offset_from_base: reader.u32_le("SetStackType OffsetFromBase")?,
},
11 => Opcode::PushType {
type_no: reader.u32_le("PushType FType")?,
},
12 => {
let cmp = CompareOp::from_byte(reader.u8("CompareType")?)?;
let into = parse_operand(reader, types)?;
let lhs = parse_operand(reader, types)?;
let rhs = parse_operand(reader, types)?;
Opcode::Compare {
op: cmp,
into,
lhs,
rhs,
}
}
13 => Opcode::CallVar {
var: parse_operand(reader, types)?,
},
14 => Opcode::SetPointer {
dest: parse_operand(reader, types)?,
src: parse_operand(reader, types)?,
},
15 => Opcode::BoolNot {
var: parse_operand(reader, types)?,
},
16 => Opcode::Negate {
var: parse_operand(reader, types)?,
},
17 => {
let var = parse_operand(reader, types)?;
let invert = reader.u8("SetFlag DoNot")? != 0;
Opcode::SetFlag { var, invert }
}
18 => Opcode::FlagGoto {
target: reader.u32_le("FlagGoto Where")?,
},
19 => Opcode::PushExceptionHandler {
finally_offset: reader.u32_le("ExceptionHandler FinallyOffset")?,
exception_offset: reader.u32_le("ExceptionHandler ExceptionOffset")?,
finally2_offset: reader.u32_le("ExceptionHandler Finally2Offset")?,
end_of_block: reader.u32_le("ExceptionHandler EndOfBlock")?,
},
20 => Opcode::PopExceptionHandler {
position: ExceptionHandlerEnd::from_byte(reader.u8("PopExceptionHandler Position")?)?,
},
21 => Opcode::IntegerNot {
var: parse_operand(reader, types)?,
},
22 => Opcode::SetStackPointerToCopy {
target: reader.u32_le("SetStackPointerToCopy Where")?,
},
23 => Opcode::Inc {
var: parse_operand(reader, types)?,
},
24 => Opcode::Dec {
var: parse_operand(reader, types)?,
},
25 => Opcode::PopAndGoto {
offset: reader.i32_le("PopAndGoto NewPosition")?,
},
26 => Opcode::Pop2AndGoto {
offset: reader.i32_le("Pop2AndGoto NewPosition")?,
},
255 => Opcode::Nop,
other => return Err(Error::UnknownBaseType { byte: other }),
};
Ok(op)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::operand::VarRef;
fn put_le32(out: &mut Vec<u8>, v: u32) {
out.extend_from_slice(&v.to_le_bytes());
}
fn put_var(out: &mut Vec<u8>, vt: u8, param: u32) {
out.push(vt);
put_le32(out, param);
}
#[test]
fn parses_pop_and_return() {
let buf = vec![4u8];
let mut r = Reader::new(&buf);
assert_eq!(parse_opcode(&mut r, &[]).unwrap(), Opcode::Pop);
let buf = vec![9u8];
let mut r = Reader::new(&buf);
assert_eq!(parse_opcode(&mut r, &[]).unwrap(), Opcode::Return);
let buf = vec![255u8];
let mut r = Reader::new(&buf);
assert_eq!(parse_opcode(&mut r, &[]).unwrap(), Opcode::Nop);
}
#[test]
fn parses_call() {
let mut buf = vec![5u8];
put_le32(&mut buf, 42);
let mut r = Reader::new(&buf);
assert_eq!(
parse_opcode(&mut r, &[]).unwrap(),
Opcode::Call { proc_no: 42 },
);
}
#[test]
fn parses_assign_with_two_var_operands() {
let mut buf = vec![0u8]; put_var(&mut buf, 0, 1); put_var(&mut buf, 0, 2); let mut r = Reader::new(&buf);
let op = parse_opcode(&mut r, &[]).unwrap();
assert_eq!(
op,
Opcode::Assign {
dest: Operand::Var(VarRef::Global(1)),
src: Operand::Var(VarRef::Global(2)),
},
);
}
#[test]
fn parses_calc_assign() {
let mut buf = vec![1u8, 2u8]; put_var(&mut buf, 0, 0);
put_var(&mut buf, 0, 1);
let mut r = Reader::new(&buf);
let op = parse_opcode(&mut r, &[]).unwrap();
assert!(matches!(
op,
Opcode::CalculateAssign {
op: CalcOp::Mul,
..
},
));
}
#[test]
fn parses_compare() {
let mut buf = vec![12u8, 5u8]; put_var(&mut buf, 0, 0); put_var(&mut buf, 0, 1); put_var(&mut buf, 0, 2); let mut r = Reader::new(&buf);
let op = parse_opcode(&mut r, &[]).unwrap();
assert!(matches!(
op,
Opcode::Compare {
op: CompareOp::Equal,
..
},
));
}
#[test]
fn parses_exception_handler() {
let mut buf = vec![19u8];
put_le32(&mut buf, 100);
put_le32(&mut buf, 200);
put_le32(&mut buf, 300);
put_le32(&mut buf, 400);
let mut r = Reader::new(&buf);
let op = parse_opcode(&mut r, &[]).unwrap();
assert_eq!(
op,
Opcode::PushExceptionHandler {
finally_offset: 100,
exception_offset: 200,
finally2_offset: 300,
end_of_block: 400,
},
);
let buf = vec![20u8, 2u8];
let mut r = Reader::new(&buf);
assert_eq!(
parse_opcode(&mut r, &[]).unwrap(),
Opcode::PopExceptionHandler {
position: ExceptionHandlerEnd::EndOfExcept,
},
);
}
#[test]
fn rejects_unknown_opcode() {
let buf = vec![100u8];
let mut r = Reader::new(&buf);
assert!(parse_opcode(&mut r, &[]).is_err());
}
}