// This file must stay in sync with encoding.csc. use crate::bytecode::{Arg, Local, Op}; use crate::data::Value; use std::io; use std::io::Read; fn read_const(prog: &mut T) -> Result { let mut buf = [0; 8]; match prog.read_exact(&mut buf) { Ok(()) => (), Err(e) => { return Err(format!("error reading input: {}", e)); } }; Ok(Value(u64::from_le_bytes(buf))) } fn read_local(prog: &mut T) -> Result { let mut buf = [0; 1]; match prog.read_exact(&mut buf) { Ok(()) => (), Err(e) => { return Err(format!("error reading input: {}", e)); } }; Ok(Local(buf[0])) } fn read_arg(prog: &mut T, is_const: bool) -> Result { if is_const { let c = read_const(prog)?; return Ok(Arg::Const(c)); } let l = read_local(prog)?; Ok(Arg::L(l)) } fn op_decoding(prog: &mut T) -> Result, String> { let mut buf = [0; 1]; match prog.read_exact(&mut buf) { Ok(()) => (), Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => { return Ok(None); } Err(e) => { return Err(format!("error reading input: {}", e)); } }; let tag = buf[0] >> 2; let arg1_const = buf[0] & 2 != 0; let arg2_const = buf[0] & 1 != 0; let op = match tag { // Note: arguments are evaluated left to right. 0 => Op::Mov(read_local(prog)?, read_arg(prog, arg1_const)?), 1 => Op::JmpIf(read_arg(prog, arg1_const)?, read_arg(prog, arg2_const)?), 2 => Op::Jmp(read_arg(prog, arg1_const)?), 3 => Op::Alloc(read_local(prog)?, read_arg(prog, arg1_const)?), 4 => Op::Peek( read_local(prog)?, read_arg(prog, arg1_const)?, read_arg(prog, arg2_const)?, ), 5 => Op::Poke( read_arg(prog, arg1_const)?, read_arg(prog, false)?, read_arg(prog, arg2_const)?, ), 6 => Op::Add( read_local(prog)?, read_arg(prog, arg1_const)?, read_arg(prog, arg2_const)?, ), 7 => Op::Sub( read_local(prog)?, read_arg(prog, arg1_const)?, read_arg(prog, arg2_const)?, ), 8 => Op::Mul( read_local(prog)?, read_arg(prog, arg1_const)?, read_arg(prog, arg2_const)?, ), 9 => Op::Div( read_local(prog)?, read_arg(prog, arg1_const)?, read_arg(prog, arg2_const)?, ), 10 => Op::Mod( read_local(prog)?, read_arg(prog, arg1_const)?, read_arg(prog, arg2_const)?, ), 11 => Op::PeekByte( read_local(prog)?, read_arg(prog, arg1_const)?, read_arg(prog, arg2_const)?, ), 12 => Op::PokeByte( read_arg(prog, arg1_const)?, read_arg(prog, false)?, read_arg(prog, arg2_const)?, ), 13 => Op::Exit(read_arg(prog, arg1_const)?), 14 => Op::AllocBytevector(read_local(prog)?, read_arg(prog, arg1_const)?), _ => { return Err(String::from("invalid opcode")); } }; Ok(Some(op)) } pub fn decode(prog: &mut T) -> Result, String> { let mut out = Vec::new(); loop { match op_decoding(prog)? { Some(op) => { out.push(op); } None => { return Ok(out); } } } } mod tests { use super::*; use Arg::*; use Op::*; #[test] fn decode_mov_const() { assert_eq!( Ok(vec![Mov(Local(0), Const(Value::from_int(10)))]), decode(&mut vec![0x2, 0, 0x15, 0, 0, 0, 0, 0, 0, 0].as_slice()) ); } #[test] fn decode_mov_local() { assert_eq!( Ok(vec![Mov(Local(0), L(Local(1)))]), decode(&mut vec![0, 0, 1].as_slice()), ); } #[test] fn decode_jmpif() { assert_eq!( Ok(vec![JmpIf(L(Local(0)), L(Local(1)))]), decode(&mut vec![4, 0, 1].as_slice()) ); } #[test] fn decode_jmp() { assert_eq!( Ok(vec![Jmp(L(Local(0)))]), decode(&mut vec![8, 0].as_slice()) ); } #[test] fn decode_alloc() { assert_eq!( Ok(vec![Alloc(Local(0), L(Local(1)))]), decode(&mut vec![12, 0, 1].as_slice()) ); } }