Experiment wih more optimizations
This commit is contained in:
parent
395f0af213
commit
1777ad298b
@ -301,7 +301,7 @@ impl<'src> Compiler<'src> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_last_operations<const COUNT: usize>(&self) -> Option<[Operation; COUNT]> {
|
fn get_last_operations<const COUNT: usize>(&self) -> Option<[Operation; COUNT]> {
|
||||||
let mut n_operations = [Operation::Return; COUNT];
|
let mut n_operations = [Operation::RETURN; COUNT];
|
||||||
|
|
||||||
for (nth, operation) in n_operations.iter_mut().rev().zip(
|
for (nth, operation) in n_operations.iter_mut().rev().zip(
|
||||||
self.instructions
|
self.instructions
|
||||||
@ -338,14 +338,14 @@ impl<'src> Compiler<'src> {
|
|||||||
|
|
||||||
let operation = instruction.operation();
|
let operation = instruction.operation();
|
||||||
|
|
||||||
if let Operation::LoadList = operation {
|
if let Operation::LOAD_LIST = operation {
|
||||||
let LoadList { start_register, .. } = LoadList::from(instruction);
|
let LoadList { start_register, .. } = LoadList::from(instruction);
|
||||||
let item_type = self.get_register_type(start_register)?;
|
let item_type = self.get_register_type(start_register)?;
|
||||||
|
|
||||||
return Ok(Type::List(Box::new(item_type)));
|
return Ok(Type::List(Box::new(item_type)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Operation::LoadSelf = operation {
|
if let Operation::LOAD_SELF = operation {
|
||||||
return Ok(Type::SelfChunk);
|
return Ok(Type::SelfChunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -612,28 +612,28 @@ impl<'src> Compiler<'src> {
|
|||||||
instruction: &Instruction,
|
instruction: &Instruction,
|
||||||
) -> Result<(Argument, bool), CompileError> {
|
) -> Result<(Argument, bool), CompileError> {
|
||||||
let (argument, push_back) = match instruction.operation() {
|
let (argument, push_back) = match instruction.operation() {
|
||||||
Operation::LoadConstant => (Argument::Constant(instruction.b_field()), false),
|
Operation::LOAD_CONSTANT => (Argument::Constant(instruction.b_field()), false),
|
||||||
Operation::GetLocal => {
|
Operation::GET_LOCAL => {
|
||||||
let local_index = instruction.b_field();
|
let local_index = instruction.b_field();
|
||||||
let (local, _) = self.get_local(local_index)?;
|
let (local, _) = self.get_local(local_index)?;
|
||||||
|
|
||||||
(Argument::Register(local.register_index), false)
|
(Argument::Register(local.register_index), false)
|
||||||
}
|
}
|
||||||
Operation::LoadBoolean
|
Operation::LOAD_BOOLEAN
|
||||||
| Operation::LoadList
|
| Operation::LOAD_LIST
|
||||||
| Operation::LoadSelf
|
| Operation::LOAD_SELF
|
||||||
| Operation::Add
|
| Operation::ADD
|
||||||
| Operation::Subtract
|
| Operation::SUBTRACT
|
||||||
| Operation::Multiply
|
| Operation::MULTIPLY
|
||||||
| Operation::Divide
|
| Operation::DIVIDE
|
||||||
| Operation::Modulo
|
| Operation::MODULO
|
||||||
| Operation::Equal
|
| Operation::EQUAL
|
||||||
| Operation::Less
|
| Operation::LESS
|
||||||
| Operation::LessEqual
|
| Operation::LESS_EQUAL
|
||||||
| Operation::Negate
|
| Operation::NEGATE
|
||||||
| Operation::Not
|
| Operation::NOT
|
||||||
| Operation::Call => (Argument::Register(instruction.a_field()), true),
|
| Operation::CALL => (Argument::Register(instruction.a_field()), true),
|
||||||
Operation::CallNative => {
|
Operation::CALL_NATIVE => {
|
||||||
let function = NativeFunction::from(instruction.b_field());
|
let function = NativeFunction::from(instruction.b_field());
|
||||||
|
|
||||||
if function.returns_value() {
|
if function.returns_value() {
|
||||||
@ -665,7 +665,7 @@ impl<'src> Compiler<'src> {
|
|||||||
position: self.previous_position,
|
position: self.previous_position,
|
||||||
})?;
|
})?;
|
||||||
let (left, push_back_left) = self.handle_binary_argument(&left_instruction)?;
|
let (left, push_back_left) = self.handle_binary_argument(&left_instruction)?;
|
||||||
let left_is_mutable_local = if let Operation::GetLocal = left_instruction.operation() {
|
let left_is_mutable_local = if let Operation::GET_LOCAL = left_instruction.operation() {
|
||||||
let GetLocal { local_index, .. } = GetLocal::from(&left_instruction);
|
let GetLocal { local_index, .. } = GetLocal::from(&left_instruction);
|
||||||
|
|
||||||
self.locals
|
self.locals
|
||||||
@ -812,7 +812,7 @@ impl<'src> Compiler<'src> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_comparison_binary(&mut self) -> Result<(), CompileError> {
|
fn parse_comparison_binary(&mut self) -> Result<(), CompileError> {
|
||||||
if let Some([Operation::Equal | Operation::Less | Operation::LessEqual, _, _]) =
|
if let Some([Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL, _, _]) =
|
||||||
self.get_last_operations()
|
self.get_last_operations()
|
||||||
{
|
{
|
||||||
return Err(CompileError::CannotChainComparison {
|
return Err(CompileError::CannotChainComparison {
|
||||||
@ -886,7 +886,7 @@ impl<'src> Compiler<'src> {
|
|||||||
fn parse_logical_binary(&mut self) -> Result<(), CompileError> {
|
fn parse_logical_binary(&mut self) -> Result<(), CompileError> {
|
||||||
let is_logic_chain = matches!(
|
let is_logic_chain = matches!(
|
||||||
self.get_last_operations(),
|
self.get_last_operations(),
|
||||||
Some([Operation::Test, Operation::Jump, _])
|
Some([Operation::TEST, Operation::JUMP, _])
|
||||||
);
|
);
|
||||||
|
|
||||||
let (mut left_instruction, left_type, left_position) = self.pop_last_instruction()?;
|
let (mut left_instruction, left_type, left_position) = self.pop_last_instruction()?;
|
||||||
@ -1204,7 +1204,7 @@ impl<'src> Compiler<'src> {
|
|||||||
match else_block_distance {
|
match else_block_distance {
|
||||||
0 => {}
|
0 => {}
|
||||||
1 => {
|
1 => {
|
||||||
if let Some([Operation::LoadBoolean | Operation::LoadConstant]) =
|
if let Some([Operation::LOAD_BOOLEAN | Operation::LOAD_CONSTANT]) =
|
||||||
self.get_last_operations()
|
self.get_last_operations()
|
||||||
{
|
{
|
||||||
let (mut loader, _, _) = self.instructions.last_mut().unwrap();
|
let (mut loader, _, _) = self.instructions.last_mut().unwrap();
|
||||||
@ -1291,10 +1291,10 @@ impl<'src> Compiler<'src> {
|
|||||||
if matches!(
|
if matches!(
|
||||||
self.get_last_operations(),
|
self.get_last_operations(),
|
||||||
Some([
|
Some([
|
||||||
Operation::Equal | Operation::Less | Operation::LessEqual,
|
Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL,
|
||||||
Operation::Jump,
|
Operation::JUMP,
|
||||||
Operation::LoadBoolean,
|
Operation::LOAD_BOOLEAN,
|
||||||
Operation::LoadBoolean,
|
Operation::LOAD_BOOLEAN,
|
||||||
],)
|
],)
|
||||||
) {
|
) {
|
||||||
self.instructions.pop();
|
self.instructions.pop();
|
||||||
|
@ -29,11 +29,11 @@ pub fn optimize_test_with_explicit_booleans(compiler: &mut Compiler) {
|
|||||||
if matches!(
|
if matches!(
|
||||||
compiler.get_last_operations(),
|
compiler.get_last_operations(),
|
||||||
Some([
|
Some([
|
||||||
Operation::Equal | Operation::Less | Operation::LessEqual,
|
Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL,
|
||||||
Operation::Test,
|
Operation::TEST,
|
||||||
Operation::Jump,
|
Operation::JUMP,
|
||||||
Operation::LoadBoolean,
|
Operation::LOAD_BOOLEAN,
|
||||||
Operation::LoadBoolean,
|
Operation::LOAD_BOOLEAN,
|
||||||
])
|
])
|
||||||
) {
|
) {
|
||||||
log::debug!("Removing redundant test, jump and boolean loaders after comparison");
|
log::debug!("Removing redundant test, jump and boolean loaders after comparison");
|
||||||
@ -70,10 +70,10 @@ pub fn optimize_test_with_loader_arguments(compiler: &mut Compiler) {
|
|||||||
if !matches!(
|
if !matches!(
|
||||||
compiler.get_last_operations(),
|
compiler.get_last_operations(),
|
||||||
Some([
|
Some([
|
||||||
Operation::Test,
|
Operation::TEST,
|
||||||
Operation::Jump,
|
Operation::JUMP,
|
||||||
Operation::LoadBoolean | Operation::LoadConstant,
|
Operation::LOAD_BOOLEAN | Operation::LOAD_CONSTANT,
|
||||||
Operation::LoadBoolean | Operation::LoadConstant,
|
Operation::LOAD_BOOLEAN | Operation::LOAD_CONSTANT,
|
||||||
])
|
])
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
|
@ -21,7 +21,7 @@ impl From<&Instruction> for Add {
|
|||||||
|
|
||||||
impl From<Add> for Instruction {
|
impl From<Add> for Instruction {
|
||||||
fn from(add: Add) -> Self {
|
fn from(add: Add) -> Self {
|
||||||
let operation = Operation::Add;
|
let operation = Operation::ADD;
|
||||||
let a = add.destination;
|
let a = add.destination;
|
||||||
let (b, b_is_constant) = add.left.as_index_and_constant_flag();
|
let (b, b_is_constant) = add.left.as_index_and_constant_flag();
|
||||||
let (c, c_is_constant) = add.right.as_index_and_constant_flag();
|
let (c, c_is_constant) = add.right.as_index_and_constant_flag();
|
||||||
|
@ -26,6 +26,6 @@ impl From<Call> for Instruction {
|
|||||||
let (b, b_is_constant) = call.function.as_index_and_constant_flag();
|
let (b, b_is_constant) = call.function.as_index_and_constant_flag();
|
||||||
let c = call.argument_count;
|
let c = call.argument_count;
|
||||||
|
|
||||||
Instruction::new(Operation::Call, a, b, c, b_is_constant, false, false)
|
Instruction::new(Operation::CALL, a, b, c, b_is_constant, false, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ impl From<&Instruction> for CallNative {
|
|||||||
|
|
||||||
impl From<CallNative> for Instruction {
|
impl From<CallNative> for Instruction {
|
||||||
fn from(call_native: CallNative) -> Self {
|
fn from(call_native: CallNative) -> Self {
|
||||||
let operation = Operation::CallNative;
|
let operation = Operation::CALL_NATIVE;
|
||||||
let a = call_native.destination;
|
let a = call_native.destination;
|
||||||
let b = call_native.function as u8;
|
let b = call_native.function as u8;
|
||||||
let c = call_native.argument_count;
|
let c = call_native.argument_count;
|
||||||
|
@ -16,7 +16,7 @@ impl From<&Instruction> for Close {
|
|||||||
|
|
||||||
impl From<Close> for Instruction {
|
impl From<Close> for Instruction {
|
||||||
fn from(close: Close) -> Self {
|
fn from(close: Close) -> Self {
|
||||||
let operation = Operation::Close;
|
let operation = Operation::CLOSE;
|
||||||
let (a, b, c) = (0, close.from, close.to);
|
let (a, b, c) = (0, close.from, close.to);
|
||||||
|
|
||||||
Instruction::new(operation, a, b, c, false, false, false)
|
Instruction::new(operation, a, b, c, false, false, false)
|
||||||
|
@ -21,7 +21,7 @@ impl From<&Instruction> for Divide {
|
|||||||
|
|
||||||
impl From<Divide> for Instruction {
|
impl From<Divide> for Instruction {
|
||||||
fn from(divide: Divide) -> Self {
|
fn from(divide: Divide) -> Self {
|
||||||
let operation = Operation::Divide;
|
let operation = Operation::DIVIDE;
|
||||||
let a = divide.destination;
|
let a = divide.destination;
|
||||||
let (b, b_is_constant) = divide.left.as_index_and_constant_flag();
|
let (b, b_is_constant) = divide.left.as_index_and_constant_flag();
|
||||||
let (c, c_is_constant) = divide.right.as_index_and_constant_flag();
|
let (c, c_is_constant) = divide.right.as_index_and_constant_flag();
|
||||||
|
@ -24,7 +24,7 @@ impl From<&Instruction> for Equal {
|
|||||||
|
|
||||||
impl From<Equal> for Instruction {
|
impl From<Equal> for Instruction {
|
||||||
fn from(equal: Equal) -> Self {
|
fn from(equal: Equal) -> Self {
|
||||||
let operation = Operation::Equal;
|
let operation = Operation::EQUAL;
|
||||||
let a = equal.destination;
|
let a = equal.destination;
|
||||||
let (b, b_is_constant) = equal.left.as_index_and_constant_flag();
|
let (b, b_is_constant) = equal.left.as_index_and_constant_flag();
|
||||||
let (c, c_is_constant) = equal.right.as_index_and_constant_flag();
|
let (c, c_is_constant) = equal.right.as_index_and_constant_flag();
|
||||||
|
@ -19,7 +19,7 @@ impl From<&Instruction> for GetLocal {
|
|||||||
|
|
||||||
impl From<GetLocal> for Instruction {
|
impl From<GetLocal> for Instruction {
|
||||||
fn from(get_local: GetLocal) -> Self {
|
fn from(get_local: GetLocal) -> Self {
|
||||||
let operation = Operation::GetLocal;
|
let operation = Operation::GET_LOCAL;
|
||||||
let a = get_local.destination;
|
let a = get_local.destination;
|
||||||
let b = get_local.local_index;
|
let b = get_local.local_index;
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ impl From<&Instruction> for Jump {
|
|||||||
|
|
||||||
impl From<Jump> for Instruction {
|
impl From<Jump> for Instruction {
|
||||||
fn from(jump: Jump) -> Self {
|
fn from(jump: Jump) -> Self {
|
||||||
let operation = Operation::Jump;
|
let operation = Operation::JUMP;
|
||||||
let b = jump.offset;
|
let b = jump.offset;
|
||||||
let c = jump.is_positive as u8;
|
let c = jump.is_positive as u8;
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ impl From<&Instruction> for Less {
|
|||||||
|
|
||||||
impl From<Less> for Instruction {
|
impl From<Less> for Instruction {
|
||||||
fn from(less: Less) -> Self {
|
fn from(less: Less) -> Self {
|
||||||
let operation = Operation::Less;
|
let operation = Operation::LESS;
|
||||||
let a = less.destination;
|
let a = less.destination;
|
||||||
let (b, b_is_constant) = less.left.as_index_and_constant_flag();
|
let (b, b_is_constant) = less.left.as_index_and_constant_flag();
|
||||||
let (c, c_is_constant) = less.right.as_index_and_constant_flag();
|
let (c, c_is_constant) = less.right.as_index_and_constant_flag();
|
||||||
|
@ -24,7 +24,7 @@ impl From<&Instruction> for LessEqual {
|
|||||||
|
|
||||||
impl From<LessEqual> for Instruction {
|
impl From<LessEqual> for Instruction {
|
||||||
fn from(less_equal: LessEqual) -> Self {
|
fn from(less_equal: LessEqual) -> Self {
|
||||||
let operation = Operation::LessEqual;
|
let operation = Operation::LESS_EQUAL;
|
||||||
let a = less_equal.destination;
|
let a = less_equal.destination;
|
||||||
let (b, b_options) = less_equal.left.as_index_and_constant_flag();
|
let (b, b_options) = less_equal.left.as_index_and_constant_flag();
|
||||||
let (c, c_options) = less_equal.right.as_index_and_constant_flag();
|
let (c, c_options) = less_equal.right.as_index_and_constant_flag();
|
||||||
|
@ -22,7 +22,7 @@ impl From<&Instruction> for LoadBoolean {
|
|||||||
|
|
||||||
impl From<LoadBoolean> for Instruction {
|
impl From<LoadBoolean> for Instruction {
|
||||||
fn from(load_boolean: LoadBoolean) -> Self {
|
fn from(load_boolean: LoadBoolean) -> Self {
|
||||||
let operation = Operation::LoadBoolean;
|
let operation = Operation::LOAD_BOOLEAN;
|
||||||
let a = load_boolean.destination;
|
let a = load_boolean.destination;
|
||||||
let b = load_boolean.value as u8;
|
let b = load_boolean.value as u8;
|
||||||
let c = load_boolean.jump_next as u8;
|
let c = load_boolean.jump_next as u8;
|
||||||
|
@ -22,7 +22,7 @@ impl From<&Instruction> for LoadConstant {
|
|||||||
|
|
||||||
impl From<LoadConstant> for Instruction {
|
impl From<LoadConstant> for Instruction {
|
||||||
fn from(load_constant: LoadConstant) -> Self {
|
fn from(load_constant: LoadConstant) -> Self {
|
||||||
let operation = Operation::LoadConstant;
|
let operation = Operation::LOAD_CONSTANT;
|
||||||
let a = load_constant.destination;
|
let a = load_constant.destination;
|
||||||
let b = load_constant.constant_index;
|
let b = load_constant.constant_index;
|
||||||
let c = load_constant.jump_next as u8;
|
let c = load_constant.jump_next as u8;
|
||||||
|
@ -19,7 +19,7 @@ impl From<&Instruction> for LoadList {
|
|||||||
|
|
||||||
impl From<LoadList> for Instruction {
|
impl From<LoadList> for Instruction {
|
||||||
fn from(load_list: LoadList) -> Self {
|
fn from(load_list: LoadList) -> Self {
|
||||||
let operation = Operation::LoadList;
|
let operation = Operation::LOAD_LIST;
|
||||||
let a = load_list.destination;
|
let a = load_list.destination;
|
||||||
let b = load_list.start_register;
|
let b = load_list.start_register;
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ impl From<&Instruction> for LoadSelf {
|
|||||||
|
|
||||||
impl From<LoadSelf> for Instruction {
|
impl From<LoadSelf> for Instruction {
|
||||||
fn from(load_self: LoadSelf) -> Self {
|
fn from(load_self: LoadSelf) -> Self {
|
||||||
let operation = Operation::LoadSelf;
|
let operation = Operation::LOAD_SELF;
|
||||||
let a = load_self.destination;
|
let a = load_self.destination;
|
||||||
|
|
||||||
Instruction::new(operation, a, 0, 0, false, false, false)
|
Instruction::new(operation, a, 0, 0, false, false, false)
|
||||||
|
@ -172,7 +172,7 @@ impl Instruction {
|
|||||||
c_is_constant: bool,
|
c_is_constant: bool,
|
||||||
d: bool,
|
d: bool,
|
||||||
) -> Instruction {
|
) -> Instruction {
|
||||||
let bits = operation as u32
|
let bits = operation.0 as u32
|
||||||
| ((b_is_constant as u32) << 5)
|
| ((b_is_constant as u32) << 5)
|
||||||
| ((c_is_constant as u32) << 6)
|
| ((c_is_constant as u32) << 6)
|
||||||
| ((d as u32) << 7)
|
| ((d as u32) << 7)
|
||||||
@ -186,7 +186,7 @@ impl Instruction {
|
|||||||
pub fn operation(&self) -> Operation {
|
pub fn operation(&self) -> Operation {
|
||||||
let operation_bits = self.0 & 0b0001_1111;
|
let operation_bits = self.0 & 0b0001_1111;
|
||||||
|
|
||||||
Operation::from(operation_bits as u8)
|
Operation(operation_bits as u8)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn b_is_constant(&self) -> bool {
|
pub fn b_is_constant(&self) -> bool {
|
||||||
@ -425,40 +425,40 @@ impl Instruction {
|
|||||||
pub fn is_math(&self) -> bool {
|
pub fn is_math(&self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
self.operation(),
|
self.operation(),
|
||||||
Operation::Add
|
Operation::ADD
|
||||||
| Operation::Subtract
|
| Operation::SUBTRACT
|
||||||
| Operation::Multiply
|
| Operation::MULTIPLY
|
||||||
| Operation::Divide
|
| Operation::DIVIDE
|
||||||
| Operation::Modulo
|
| Operation::MODULO
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_comparison(&self) -> bool {
|
pub fn is_comparison(&self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
self.operation(),
|
self.operation(),
|
||||||
Operation::Equal | Operation::Less | Operation::LessEqual
|
Operation::EQUAL | Operation::LESS | Operation::LESS_EQUAL
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_argument(&self) -> Option<Argument> {
|
pub fn as_argument(&self) -> Option<Argument> {
|
||||||
match self.operation() {
|
match self.operation() {
|
||||||
Operation::LoadConstant => Some(Argument::Constant(self.b_field())),
|
Operation::LOAD_CONSTANT => Some(Argument::Constant(self.b_field())),
|
||||||
Operation::LoadBoolean
|
Operation::LOAD_BOOLEAN
|
||||||
| Operation::LoadList
|
| Operation::LOAD_LIST
|
||||||
| Operation::LoadSelf
|
| Operation::LOAD_SELF
|
||||||
| Operation::GetLocal
|
| Operation::GET_LOCAL
|
||||||
| Operation::Add
|
| Operation::ADD
|
||||||
| Operation::Subtract
|
| Operation::SUBTRACT
|
||||||
| Operation::Multiply
|
| Operation::MULTIPLY
|
||||||
| Operation::Divide
|
| Operation::DIVIDE
|
||||||
| Operation::Modulo
|
| Operation::MODULO
|
||||||
| Operation::Equal
|
| Operation::EQUAL
|
||||||
| Operation::Less
|
| Operation::LESS
|
||||||
| Operation::LessEqual
|
| Operation::LESS_EQUAL
|
||||||
| Operation::Negate
|
| Operation::NEGATE
|
||||||
| Operation::Not
|
| Operation::NOT
|
||||||
| Operation::Call => Some(Argument::Register(self.a_field())),
|
| Operation::CALL => Some(Argument::Register(self.a_field())),
|
||||||
Operation::CallNative => {
|
Operation::CALL_NATIVE => {
|
||||||
let function = NativeFunction::from(self.b_field());
|
let function = NativeFunction::from(self.b_field());
|
||||||
|
|
||||||
if function.returns_value() {
|
if function.returns_value() {
|
||||||
@ -496,50 +496,51 @@ impl Instruction {
|
|||||||
|
|
||||||
pub fn yields_value(&self) -> bool {
|
pub fn yields_value(&self) -> bool {
|
||||||
match self.operation() {
|
match self.operation() {
|
||||||
Operation::LoadBoolean
|
Operation::LOAD_BOOLEAN
|
||||||
| Operation::LoadConstant
|
| Operation::LOAD_CONSTANT
|
||||||
| Operation::LoadList
|
| Operation::LOAD_LIST
|
||||||
| Operation::LoadSelf
|
| Operation::LOAD_SELF
|
||||||
| Operation::GetLocal
|
| Operation::GET_LOCAL
|
||||||
| Operation::Add
|
| Operation::ADD
|
||||||
| Operation::Subtract
|
| Operation::SUBTRACT
|
||||||
| Operation::Multiply
|
| Operation::MULTIPLY
|
||||||
| Operation::Divide
|
| Operation::DIVIDE
|
||||||
| Operation::Modulo
|
| Operation::MODULO
|
||||||
| Operation::Negate
|
| Operation::NEGATE
|
||||||
| Operation::Not
|
| Operation::NOT
|
||||||
| Operation::Equal
|
| Operation::EQUAL
|
||||||
| Operation::Less
|
| Operation::LESS
|
||||||
| Operation::LessEqual
|
| Operation::LESS_EQUAL
|
||||||
| Operation::Call => true,
|
| Operation::CALL => true,
|
||||||
Operation::CallNative => {
|
Operation::CALL_NATIVE => {
|
||||||
let function = NativeFunction::from(self.b_field());
|
let function = NativeFunction::from(self.b_field());
|
||||||
|
|
||||||
function.returns_value()
|
function.returns_value()
|
||||||
}
|
}
|
||||||
Operation::Move
|
Operation::MOVE
|
||||||
| Operation::Close
|
| Operation::CLOSE
|
||||||
| Operation::SetLocal
|
| Operation::SET_LOCAL
|
||||||
| Operation::Test
|
| Operation::TEST
|
||||||
| Operation::TestSet
|
| Operation::TEST_SET
|
||||||
| Operation::Jump
|
| Operation::JUMP
|
||||||
| Operation::Return => false,
|
| Operation::RETURN => false,
|
||||||
|
_ => Operation::panic_from_unknown_code(self.operation().0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disassembly_info(&self) -> String {
|
pub fn disassembly_info(&self) -> String {
|
||||||
match self.operation() {
|
match self.operation() {
|
||||||
Operation::Move => {
|
Operation::MOVE => {
|
||||||
let Move { from, to } = Move::from(self);
|
let Move { from, to } = Move::from(self);
|
||||||
|
|
||||||
format!("R{to} = R{from}")
|
format!("R{to} = R{from}")
|
||||||
}
|
}
|
||||||
Operation::Close => {
|
Operation::CLOSE => {
|
||||||
let Close { from, to } = Close::from(self);
|
let Close { from, to } = Close::from(self);
|
||||||
|
|
||||||
format!("R{from}..R{to}")
|
format!("R{from}..R{to}")
|
||||||
}
|
}
|
||||||
Operation::LoadBoolean => {
|
Operation::LOAD_BOOLEAN => {
|
||||||
let LoadBoolean {
|
let LoadBoolean {
|
||||||
destination,
|
destination,
|
||||||
value,
|
value,
|
||||||
@ -552,7 +553,7 @@ impl Instruction {
|
|||||||
format!("R{destination} = {value}")
|
format!("R{destination} = {value}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Operation::LoadConstant => {
|
Operation::LOAD_CONSTANT => {
|
||||||
let LoadConstant {
|
let LoadConstant {
|
||||||
destination,
|
destination,
|
||||||
constant_index,
|
constant_index,
|
||||||
@ -565,7 +566,7 @@ impl Instruction {
|
|||||||
format!("R{destination} = C{constant_index}")
|
format!("R{destination} = C{constant_index}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Operation::LoadList => {
|
Operation::LOAD_LIST => {
|
||||||
let LoadList {
|
let LoadList {
|
||||||
destination,
|
destination,
|
||||||
start_register,
|
start_register,
|
||||||
@ -574,12 +575,12 @@ impl Instruction {
|
|||||||
|
|
||||||
format!("R{destination} = [R{start_register}..=R{end_register}]",)
|
format!("R{destination} = [R{start_register}..=R{end_register}]",)
|
||||||
}
|
}
|
||||||
Operation::LoadSelf => {
|
Operation::LOAD_SELF => {
|
||||||
let LoadSelf { destination } = LoadSelf::from(self);
|
let LoadSelf { destination } = LoadSelf::from(self);
|
||||||
|
|
||||||
format!("R{destination} = self")
|
format!("R{destination} = self")
|
||||||
}
|
}
|
||||||
Operation::GetLocal => {
|
Operation::GET_LOCAL => {
|
||||||
let GetLocal {
|
let GetLocal {
|
||||||
destination,
|
destination,
|
||||||
local_index,
|
local_index,
|
||||||
@ -587,7 +588,7 @@ impl Instruction {
|
|||||||
|
|
||||||
format!("R{destination} = L{local_index}")
|
format!("R{destination} = L{local_index}")
|
||||||
}
|
}
|
||||||
Operation::SetLocal => {
|
Operation::SET_LOCAL => {
|
||||||
let SetLocal {
|
let SetLocal {
|
||||||
register_index,
|
register_index,
|
||||||
local_index,
|
local_index,
|
||||||
@ -595,7 +596,7 @@ impl Instruction {
|
|||||||
|
|
||||||
format!("L{local_index} = R{register_index}")
|
format!("L{local_index} = R{register_index}")
|
||||||
}
|
}
|
||||||
Operation::Add => {
|
Operation::ADD => {
|
||||||
let Add {
|
let Add {
|
||||||
destination,
|
destination,
|
||||||
left,
|
left,
|
||||||
@ -604,7 +605,7 @@ impl Instruction {
|
|||||||
|
|
||||||
format!("R{destination} = {left} + {right}")
|
format!("R{destination} = {left} + {right}")
|
||||||
}
|
}
|
||||||
Operation::Subtract => {
|
Operation::SUBTRACT => {
|
||||||
let Subtract {
|
let Subtract {
|
||||||
destination,
|
destination,
|
||||||
left,
|
left,
|
||||||
@ -613,7 +614,7 @@ impl Instruction {
|
|||||||
|
|
||||||
format!("R{destination} = {left} - {right}")
|
format!("R{destination} = {left} - {right}")
|
||||||
}
|
}
|
||||||
Operation::Multiply => {
|
Operation::MULTIPLY => {
|
||||||
let Multiply {
|
let Multiply {
|
||||||
destination,
|
destination,
|
||||||
left,
|
left,
|
||||||
@ -622,7 +623,7 @@ impl Instruction {
|
|||||||
|
|
||||||
format!("R{destination} = {left} * {right}")
|
format!("R{destination} = {left} * {right}")
|
||||||
}
|
}
|
||||||
Operation::Divide => {
|
Operation::DIVIDE => {
|
||||||
let Divide {
|
let Divide {
|
||||||
destination,
|
destination,
|
||||||
left,
|
left,
|
||||||
@ -631,7 +632,7 @@ impl Instruction {
|
|||||||
|
|
||||||
format!("R{destination} = {left} / {right}")
|
format!("R{destination} = {left} / {right}")
|
||||||
}
|
}
|
||||||
Operation::Modulo => {
|
Operation::MODULO => {
|
||||||
let Modulo {
|
let Modulo {
|
||||||
destination,
|
destination,
|
||||||
left,
|
left,
|
||||||
@ -640,7 +641,7 @@ impl Instruction {
|
|||||||
|
|
||||||
format!("R{destination} = {left} % {right}")
|
format!("R{destination} = {left} % {right}")
|
||||||
}
|
}
|
||||||
Operation::Test => {
|
Operation::TEST => {
|
||||||
let Test {
|
let Test {
|
||||||
argument,
|
argument,
|
||||||
test_value: value,
|
test_value: value,
|
||||||
@ -649,7 +650,7 @@ impl Instruction {
|
|||||||
|
|
||||||
format!("if {bang}{argument} {{ JUMP +1 }}",)
|
format!("if {bang}{argument} {{ JUMP +1 }}",)
|
||||||
}
|
}
|
||||||
Operation::TestSet => {
|
Operation::TEST_SET => {
|
||||||
let TestSet {
|
let TestSet {
|
||||||
destination,
|
destination,
|
||||||
argument,
|
argument,
|
||||||
@ -659,7 +660,7 @@ impl Instruction {
|
|||||||
|
|
||||||
format!("if {bang}{argument} {{ JUMP +1 }} else {{ R{destination} = {argument} }}")
|
format!("if {bang}{argument} {{ JUMP +1 }} else {{ R{destination} = {argument} }}")
|
||||||
}
|
}
|
||||||
Operation::Equal => {
|
Operation::EQUAL => {
|
||||||
let Equal {
|
let Equal {
|
||||||
destination,
|
destination,
|
||||||
value,
|
value,
|
||||||
@ -670,7 +671,7 @@ impl Instruction {
|
|||||||
|
|
||||||
format!("R{destination} = {left} {comparison_symbol} {right}")
|
format!("R{destination} = {left} {comparison_symbol} {right}")
|
||||||
}
|
}
|
||||||
Operation::Less => {
|
Operation::LESS => {
|
||||||
let Less {
|
let Less {
|
||||||
destination,
|
destination,
|
||||||
value,
|
value,
|
||||||
@ -681,7 +682,7 @@ impl Instruction {
|
|||||||
|
|
||||||
format!("R{destination} = {left} {comparison_symbol} {right}")
|
format!("R{destination} = {left} {comparison_symbol} {right}")
|
||||||
}
|
}
|
||||||
Operation::LessEqual => {
|
Operation::LESS_EQUAL => {
|
||||||
let LessEqual {
|
let LessEqual {
|
||||||
destination,
|
destination,
|
||||||
value,
|
value,
|
||||||
@ -692,7 +693,7 @@ impl Instruction {
|
|||||||
|
|
||||||
format!("R{destination} = {left} {comparison_symbol} {right}")
|
format!("R{destination} = {left} {comparison_symbol} {right}")
|
||||||
}
|
}
|
||||||
Operation::Negate => {
|
Operation::NEGATE => {
|
||||||
let Negate {
|
let Negate {
|
||||||
destination,
|
destination,
|
||||||
argument,
|
argument,
|
||||||
@ -700,7 +701,7 @@ impl Instruction {
|
|||||||
|
|
||||||
format!("R{destination} = -{argument}")
|
format!("R{destination} = -{argument}")
|
||||||
}
|
}
|
||||||
Operation::Not => {
|
Operation::NOT => {
|
||||||
let Not {
|
let Not {
|
||||||
destination,
|
destination,
|
||||||
argument,
|
argument,
|
||||||
@ -708,7 +709,7 @@ impl Instruction {
|
|||||||
|
|
||||||
format!("R{destination} = !{argument}")
|
format!("R{destination} = !{argument}")
|
||||||
}
|
}
|
||||||
Operation::Jump => {
|
Operation::JUMP => {
|
||||||
let Jump {
|
let Jump {
|
||||||
offset,
|
offset,
|
||||||
is_positive,
|
is_positive,
|
||||||
@ -720,7 +721,7 @@ impl Instruction {
|
|||||||
format!("JUMP -{offset}")
|
format!("JUMP -{offset}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Operation::Call => {
|
Operation::CALL => {
|
||||||
let Call {
|
let Call {
|
||||||
destination,
|
destination,
|
||||||
function,
|
function,
|
||||||
@ -737,7 +738,7 @@ impl Instruction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Operation::CallNative => {
|
Operation::CALL_NATIVE => {
|
||||||
let CallNative {
|
let CallNative {
|
||||||
destination,
|
destination,
|
||||||
function,
|
function,
|
||||||
@ -763,7 +764,7 @@ impl Instruction {
|
|||||||
|
|
||||||
info_string
|
info_string
|
||||||
}
|
}
|
||||||
Operation::Return => {
|
Operation::RETURN => {
|
||||||
let Return {
|
let Return {
|
||||||
should_return_value,
|
should_return_value,
|
||||||
} = Return::from(self);
|
} = Return::from(self);
|
||||||
|
@ -21,7 +21,7 @@ impl From<&Instruction> for Modulo {
|
|||||||
|
|
||||||
impl From<Modulo> for Instruction {
|
impl From<Modulo> for Instruction {
|
||||||
fn from(modulo: Modulo) -> Self {
|
fn from(modulo: Modulo) -> Self {
|
||||||
let operation = Operation::Modulo;
|
let operation = Operation::MODULO;
|
||||||
let a = modulo.destination;
|
let a = modulo.destination;
|
||||||
let (b, b_is_constant) = modulo.left.as_index_and_constant_flag();
|
let (b, b_is_constant) = modulo.left.as_index_and_constant_flag();
|
||||||
let (c, c_is_constant) = modulo.right.as_index_and_constant_flag();
|
let (c, c_is_constant) = modulo.right.as_index_and_constant_flag();
|
||||||
|
@ -16,7 +16,7 @@ impl From<&Instruction> for Move {
|
|||||||
|
|
||||||
impl From<Move> for Instruction {
|
impl From<Move> for Instruction {
|
||||||
fn from(r#move: Move) -> Self {
|
fn from(r#move: Move) -> Self {
|
||||||
let operation = Operation::Move;
|
let operation = Operation::MOVE;
|
||||||
let b = r#move.from;
|
let b = r#move.from;
|
||||||
let c = r#move.to;
|
let c = r#move.to;
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ impl From<&Instruction> for Multiply {
|
|||||||
|
|
||||||
impl From<Multiply> for Instruction {
|
impl From<Multiply> for Instruction {
|
||||||
fn from(multiply: Multiply) -> Self {
|
fn from(multiply: Multiply) -> Self {
|
||||||
let operation = Operation::Multiply;
|
let operation = Operation::MULTIPLY;
|
||||||
let a = multiply.destination;
|
let a = multiply.destination;
|
||||||
let (b, b_options) = multiply.left.as_index_and_constant_flag();
|
let (b, b_options) = multiply.left.as_index_and_constant_flag();
|
||||||
let (c, c_options) = multiply.right.as_index_and_constant_flag();
|
let (c, c_options) = multiply.right.as_index_and_constant_flag();
|
||||||
|
@ -19,7 +19,7 @@ impl From<&Instruction> for Negate {
|
|||||||
|
|
||||||
impl From<Negate> for Instruction {
|
impl From<Negate> for Instruction {
|
||||||
fn from(negate: Negate) -> Self {
|
fn from(negate: Negate) -> Self {
|
||||||
let operation = Operation::Negate;
|
let operation = Operation::NEGATE;
|
||||||
let a = negate.destination;
|
let a = negate.destination;
|
||||||
let (b, b_is_constant) = negate.argument.as_index_and_constant_flag();
|
let (b, b_is_constant) = negate.argument.as_index_and_constant_flag();
|
||||||
let c = 0;
|
let c = 0;
|
||||||
|
@ -19,7 +19,7 @@ impl From<&Instruction> for Not {
|
|||||||
|
|
||||||
impl From<Not> for Instruction {
|
impl From<Not> for Instruction {
|
||||||
fn from(not: Not) -> Self {
|
fn from(not: Not) -> Self {
|
||||||
let operation = Operation::Not;
|
let operation = Operation::NOT;
|
||||||
let a = not.destination;
|
let a = not.destination;
|
||||||
let (b, b_is_constant) = not.argument.as_index_and_constant_flag();
|
let (b, b_is_constant) = not.argument.as_index_and_constant_flag();
|
||||||
|
|
||||||
|
@ -4,128 +4,71 @@ use std::fmt::{self, Debug, Display, Formatter};
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub const MOVE_BYTE: u8 = 0;
|
|
||||||
pub const CLOSE_BYTE: u8 = 1;
|
|
||||||
pub const LOAD_BOOLEAN_BYTE: u8 = 2;
|
|
||||||
pub const LOAD_CONSTANT_BYTE: u8 = 3;
|
|
||||||
pub const LOAD_LIST_BYTE: u8 = 4;
|
|
||||||
pub const LOAD_SELF_BYTE: u8 = 5;
|
|
||||||
pub const GET_LOCAL_BYTE: u8 = 6;
|
|
||||||
pub const SET_LOCAL_BYTE: u8 = 7;
|
|
||||||
pub const ADD_BYTE: u8 = 8;
|
|
||||||
pub const SUBTRACT_BYTE: u8 = 9;
|
|
||||||
pub const MULTIPLY_BYTE: u8 = 10;
|
|
||||||
pub const DIVIDE_BYTE: u8 = 11;
|
|
||||||
pub const MODULO_BYTE: u8 = 12;
|
|
||||||
pub const TEST_BYTE: u8 = 13;
|
|
||||||
pub const TEST_SET_BYTE: u8 = 14;
|
|
||||||
pub const EQUAL_BYTE: u8 = 15;
|
|
||||||
pub const LESS_BYTE: u8 = 16;
|
|
||||||
pub const LESS_EQUAL_BYTE: u8 = 17;
|
|
||||||
pub const NEGATE_BYTE: u8 = 18;
|
|
||||||
pub const NOT_BYTE: u8 = 19;
|
|
||||||
pub const CALL_BYTE: u8 = 20;
|
|
||||||
pub const CALL_NATIVE_BYTE: u8 = 21;
|
|
||||||
pub const JUMP_BYTE: u8 = 22;
|
|
||||||
pub const RETURN_BYTE: u8 = 23;
|
|
||||||
|
|
||||||
/// Part of an [Instruction][crate::Instruction] that is encoded as a single byte.
|
/// Part of an [Instruction][crate::Instruction] that is encoded as a single byte.
|
||||||
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
#[repr(u8)]
|
pub struct Operation(pub u8);
|
||||||
pub enum Operation {
|
|
||||||
Move = MOVE_BYTE,
|
|
||||||
Close = CLOSE_BYTE,
|
|
||||||
LoadBoolean = LOAD_BOOLEAN_BYTE,
|
|
||||||
LoadConstant = LOAD_CONSTANT_BYTE,
|
|
||||||
LoadList = LOAD_LIST_BYTE,
|
|
||||||
LoadSelf = LOAD_SELF_BYTE,
|
|
||||||
GetLocal = GET_LOCAL_BYTE,
|
|
||||||
SetLocal = SET_LOCAL_BYTE,
|
|
||||||
Add = ADD_BYTE,
|
|
||||||
Subtract = SUBTRACT_BYTE,
|
|
||||||
Multiply = MULTIPLY_BYTE,
|
|
||||||
Divide = DIVIDE_BYTE,
|
|
||||||
Modulo = MODULO_BYTE,
|
|
||||||
Test = TEST_BYTE,
|
|
||||||
TestSet = TEST_SET_BYTE,
|
|
||||||
Equal = EQUAL_BYTE,
|
|
||||||
Less = LESS_BYTE,
|
|
||||||
LessEqual = LESS_EQUAL_BYTE,
|
|
||||||
Negate = NEGATE_BYTE,
|
|
||||||
Not = NOT_BYTE,
|
|
||||||
Call = CALL_BYTE,
|
|
||||||
CallNative = CALL_NATIVE_BYTE,
|
|
||||||
Jump = JUMP_BYTE,
|
|
||||||
Return = RETURN_BYTE,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u8> for Operation {
|
impl Operation {
|
||||||
fn from(byte: u8) -> Self {
|
pub const MOVE: Operation = Operation(0);
|
||||||
match byte {
|
pub const CLOSE: Operation = Operation(1);
|
||||||
MOVE_BYTE => Self::Move,
|
pub const LOAD_BOOLEAN: Operation = Operation(2);
|
||||||
CLOSE_BYTE => Self::Close,
|
pub const LOAD_CONSTANT: Operation = Operation(3);
|
||||||
LOAD_BOOLEAN_BYTE => Self::LoadBoolean,
|
pub const LOAD_LIST: Operation = Operation(4);
|
||||||
LOAD_CONSTANT_BYTE => Self::LoadConstant,
|
pub const LOAD_SELF: Operation = Operation(5);
|
||||||
LOAD_LIST_BYTE => Self::LoadList,
|
pub const GET_LOCAL: Operation = Operation(6);
|
||||||
LOAD_SELF_BYTE => Self::LoadSelf,
|
pub const SET_LOCAL: Operation = Operation(7);
|
||||||
GET_LOCAL_BYTE => Self::GetLocal,
|
pub const ADD: Operation = Operation(8);
|
||||||
SET_LOCAL_BYTE => Self::SetLocal,
|
pub const SUBTRACT: Operation = Operation(9);
|
||||||
ADD_BYTE => Self::Add,
|
pub const MULTIPLY: Operation = Operation(10);
|
||||||
SUBTRACT_BYTE => Self::Subtract,
|
pub const DIVIDE: Operation = Operation(11);
|
||||||
MULTIPLY_BYTE => Self::Multiply,
|
pub const MODULO: Operation = Operation(12);
|
||||||
DIVIDE_BYTE => Self::Divide,
|
pub const TEST: Operation = Operation(13);
|
||||||
MODULO_BYTE => Self::Modulo,
|
pub const TEST_SET: Operation = Operation(14);
|
||||||
TEST_BYTE => Self::Test,
|
pub const EQUAL: Operation = Operation(15);
|
||||||
TEST_SET_BYTE => Self::TestSet,
|
pub const LESS: Operation = Operation(16);
|
||||||
EQUAL_BYTE => Self::Equal,
|
pub const LESS_EQUAL: Operation = Operation(17);
|
||||||
LESS_BYTE => Self::Less,
|
pub const NEGATE: Operation = Operation(18);
|
||||||
LESS_EQUAL_BYTE => Self::LessEqual,
|
pub const NOT: Operation = Operation(19);
|
||||||
NEGATE_BYTE => Self::Negate,
|
pub const CALL: Operation = Operation(20);
|
||||||
NOT_BYTE => Self::Not,
|
pub const CALL_NATIVE: Operation = Operation(21);
|
||||||
CALL_BYTE => Self::Call,
|
pub const JUMP: Operation = Operation(22);
|
||||||
CALL_NATIVE_BYTE => Self::CallNative,
|
pub const RETURN: Operation = Operation(23);
|
||||||
JUMP_BYTE => Self::Jump,
|
|
||||||
RETURN_BYTE => Self::Return,
|
|
||||||
_ => {
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
panic!("Invalid operation byte: {}", byte)
|
|
||||||
} else {
|
|
||||||
Self::Return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Operation {
|
impl Operation {
|
||||||
pub fn name(self) -> &'static str {
|
pub fn name(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Move => "MOVE",
|
Self::MOVE => "MOVE",
|
||||||
Self::Close => "CLOSE",
|
Self::CLOSE => "CLOSE",
|
||||||
Self::LoadBoolean => "LOAD_BOOLEAN",
|
Self::LOAD_BOOLEAN => "LOAD_BOOLEAN",
|
||||||
Self::LoadConstant => "LOAD_CONSTANT",
|
Self::LOAD_CONSTANT => "LOAD_CONSTANT",
|
||||||
Self::LoadList => "LOAD_LIST",
|
Self::LOAD_LIST => "LOAD_LIST",
|
||||||
Self::LoadSelf => "LOAD_SELF",
|
Self::LOAD_SELF => "LOAD_SELF",
|
||||||
Self::GetLocal => "GET_LOCAL",
|
Self::GET_LOCAL => "GET_LOCAL",
|
||||||
Self::SetLocal => "SET_LOCAL",
|
Self::SET_LOCAL => "SET_LOCAL",
|
||||||
Self::Add => "ADD",
|
Self::ADD => "ADD",
|
||||||
Self::Subtract => "SUBTRACT",
|
Self::SUBTRACT => "SUBTRACT",
|
||||||
Self::Multiply => "MULTIPLY",
|
Self::MULTIPLY => "MULTIPLY",
|
||||||
Self::Divide => "DIVIDE",
|
Self::DIVIDE => "DIVIDE",
|
||||||
Self::Modulo => "MODULO",
|
Self::MODULO => "MODULO",
|
||||||
Self::Test => "TEST",
|
Self::TEST => "TEST",
|
||||||
Self::TestSet => "TEST_SET",
|
Self::TEST_SET => "TEST_SET",
|
||||||
Self::Equal => "EQUAL",
|
Self::EQUAL => "EQUAL",
|
||||||
Self::Less => "LESS",
|
Self::LESS => "LESS",
|
||||||
Self::LessEqual => "LESS_EQUAL",
|
Self::LESS_EQUAL => "LESS_EQUAL",
|
||||||
Self::Negate => "NEGATE",
|
Self::NEGATE => "NEGATE",
|
||||||
Self::Not => "NOT",
|
Self::NOT => "NOT",
|
||||||
Self::Call => "CALL",
|
Self::CALL => "CALL",
|
||||||
Self::CallNative => "CALL_NATIVE",
|
Self::CALL_NATIVE => "CALL_NATIVE",
|
||||||
Self::Jump => "JUMP",
|
Self::JUMP => "JUMP",
|
||||||
Self::Return => "RETURN",
|
Self::RETURN => "RETURN",
|
||||||
|
_ => Self::panic_from_unknown_code(self.0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn panic_from_unknown_code(code: u8) -> ! {
|
||||||
|
panic!("Unknown operation code: {code}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Operation {
|
impl Debug for Operation {
|
||||||
@ -139,54 +82,3 @@ impl Display for Operation {
|
|||||||
write!(f, "{}", self.name())
|
write!(f, "{}", self.name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
const ALL_OPERATIONS: [Operation; 24] = [
|
|
||||||
Operation::Move,
|
|
||||||
Operation::Close,
|
|
||||||
Operation::LoadBoolean,
|
|
||||||
Operation::LoadConstant,
|
|
||||||
Operation::LoadList,
|
|
||||||
Operation::LoadSelf,
|
|
||||||
Operation::GetLocal,
|
|
||||||
Operation::SetLocal,
|
|
||||||
Operation::Add,
|
|
||||||
Operation::Subtract,
|
|
||||||
Operation::Multiply,
|
|
||||||
Operation::Divide,
|
|
||||||
Operation::Modulo,
|
|
||||||
Operation::Test,
|
|
||||||
Operation::TestSet,
|
|
||||||
Operation::Equal,
|
|
||||||
Operation::Less,
|
|
||||||
Operation::LessEqual,
|
|
||||||
Operation::Negate,
|
|
||||||
Operation::Not,
|
|
||||||
Operation::Call,
|
|
||||||
Operation::CallNative,
|
|
||||||
Operation::Jump,
|
|
||||||
Operation::Return,
|
|
||||||
];
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn operations_are_unique() {
|
|
||||||
for (i, operation) in ALL_OPERATIONS.into_iter().enumerate() {
|
|
||||||
assert_eq!(i, operation as usize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn operation_uses_five_bits() {
|
|
||||||
for operation in ALL_OPERATIONS {
|
|
||||||
assert_eq!(operation as u8 & 0b1110_0000, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn operation_is_one_byte() {
|
|
||||||
assert_eq!(size_of::<Operation>(), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -16,7 +16,7 @@ impl From<&Instruction> for Return {
|
|||||||
|
|
||||||
impl From<Return> for Instruction {
|
impl From<Return> for Instruction {
|
||||||
fn from(r#return: Return) -> Self {
|
fn from(r#return: Return) -> Self {
|
||||||
let operation = Operation::Return;
|
let operation = Operation::RETURN;
|
||||||
let b = r#return.should_return_value as u8;
|
let b = r#return.should_return_value as u8;
|
||||||
|
|
||||||
Instruction::new(operation, 0, b, 0, false, false, false)
|
Instruction::new(operation, 0, b, 0, false, false, false)
|
||||||
|
@ -19,7 +19,7 @@ impl From<&Instruction> for SetLocal {
|
|||||||
|
|
||||||
impl From<SetLocal> for Instruction {
|
impl From<SetLocal> for Instruction {
|
||||||
fn from(set_local: SetLocal) -> Self {
|
fn from(set_local: SetLocal) -> Self {
|
||||||
let operation = Operation::SetLocal;
|
let operation = Operation::SET_LOCAL;
|
||||||
let b = set_local.register_index;
|
let b = set_local.register_index;
|
||||||
let c = set_local.local_index;
|
let c = set_local.local_index;
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ impl From<&Instruction> for Subtract {
|
|||||||
|
|
||||||
impl From<Subtract> for Instruction {
|
impl From<Subtract> for Instruction {
|
||||||
fn from(subtract: Subtract) -> Self {
|
fn from(subtract: Subtract) -> Self {
|
||||||
let operation = Operation::Subtract;
|
let operation = Operation::SUBTRACT;
|
||||||
let a = subtract.destination;
|
let a = subtract.destination;
|
||||||
let (b, b_is_constant) = subtract.left.as_index_and_constant_flag();
|
let (b, b_is_constant) = subtract.left.as_index_and_constant_flag();
|
||||||
let (c, c_is_constant) = subtract.right.as_index_and_constant_flag();
|
let (c, c_is_constant) = subtract.right.as_index_and_constant_flag();
|
||||||
|
@ -19,7 +19,7 @@ impl From<&Instruction> for Test {
|
|||||||
|
|
||||||
impl From<Test> for Instruction {
|
impl From<Test> for Instruction {
|
||||||
fn from(test: Test) -> Self {
|
fn from(test: Test) -> Self {
|
||||||
let operation = Operation::Test;
|
let operation = Operation::TEST;
|
||||||
let (b, b_is_constant) = test.argument.as_index_and_constant_flag();
|
let (b, b_is_constant) = test.argument.as_index_and_constant_flag();
|
||||||
let c = test.test_value as u8;
|
let c = test.test_value as u8;
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ impl From<&Instruction> for TestSet {
|
|||||||
|
|
||||||
impl From<TestSet> for Instruction {
|
impl From<TestSet> for Instruction {
|
||||||
fn from(test_set: TestSet) -> Self {
|
fn from(test_set: TestSet) -> Self {
|
||||||
let operation = Operation::Test;
|
let operation = Operation::TEST;
|
||||||
let a = test_set.destination;
|
let a = test_set.destination;
|
||||||
let (b, b_is_constant) = test_set.argument.as_index_and_constant_flag();
|
let (b, b_is_constant) = test_set.argument.as_index_and_constant_flag();
|
||||||
let c = test_set.test_value as u8;
|
let c = test_set.test_value as u8;
|
||||||
|
@ -90,6 +90,7 @@ impl<'a> Vm<'a> {
|
|||||||
position
|
position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn r#move<'b, 'c>(
|
fn r#move<'b, 'c>(
|
||||||
vm: &'b mut Vm<'c>,
|
vm: &'b mut Vm<'c>,
|
||||||
instruction_data: InstructionData,
|
instruction_data: InstructionData,
|
||||||
@ -108,6 +109,7 @@ impl<'a> Vm<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn close<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
|
fn close<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
|
||||||
let InstructionData { b, c, .. } = instruction_data;
|
let InstructionData { b, c, .. } = instruction_data;
|
||||||
|
|
||||||
@ -124,6 +126,7 @@ impl<'a> Vm<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn load_boolean<'b, 'c>(
|
fn load_boolean<'b, 'c>(
|
||||||
vm: &'b mut Vm<'c>,
|
vm: &'b mut Vm<'c>,
|
||||||
instruction_data: InstructionData,
|
instruction_data: InstructionData,
|
||||||
@ -141,6 +144,7 @@ impl<'a> Vm<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn load_constant<'b, 'c>(
|
fn load_constant<'b, 'c>(
|
||||||
vm: &'b mut Vm<'c>,
|
vm: &'b mut Vm<'c>,
|
||||||
instruction_data: InstructionData,
|
instruction_data: InstructionData,
|
||||||
@ -157,6 +161,7 @@ impl<'a> Vm<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn load_list<'b, 'c>(
|
fn load_list<'b, 'c>(
|
||||||
vm: &'b mut Vm<'c>,
|
vm: &'b mut Vm<'c>,
|
||||||
instruction_data: InstructionData,
|
instruction_data: InstructionData,
|
||||||
@ -181,6 +186,7 @@ impl<'a> Vm<'a> {
|
|||||||
vm.set_register(a, register)
|
vm.set_register(a, register)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn load_self<'b, 'c>(
|
fn load_self<'b, 'c>(
|
||||||
vm: &'b mut Vm<'c>,
|
vm: &'b mut Vm<'c>,
|
||||||
instruction_data: InstructionData,
|
instruction_data: InstructionData,
|
||||||
@ -191,6 +197,7 @@ impl<'a> Vm<'a> {
|
|||||||
vm.set_register(a, register)
|
vm.set_register(a, register)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn get_local<'b, 'c>(
|
fn get_local<'b, 'c>(
|
||||||
vm: &'b mut Vm<'c>,
|
vm: &'b mut Vm<'c>,
|
||||||
instruction_data: InstructionData,
|
instruction_data: InstructionData,
|
||||||
@ -202,6 +209,7 @@ impl<'a> Vm<'a> {
|
|||||||
vm.set_register(a, register)
|
vm.set_register(a, register)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn set_local<'b, 'c>(
|
fn set_local<'b, 'c>(
|
||||||
vm: &'b mut Vm<'c>,
|
vm: &'b mut Vm<'c>,
|
||||||
instruction_data: InstructionData,
|
instruction_data: InstructionData,
|
||||||
@ -213,6 +221,7 @@ impl<'a> Vm<'a> {
|
|||||||
vm.set_register(local_register_index, register)
|
vm.set_register(local_register_index, register)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn add<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
|
fn add<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
|
||||||
let InstructionData {
|
let InstructionData {
|
||||||
a,
|
a,
|
||||||
@ -239,6 +248,7 @@ impl<'a> Vm<'a> {
|
|||||||
vm.set_register(a, register)
|
vm.set_register(a, register)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn subtract<'b, 'c>(
|
fn subtract<'b, 'c>(
|
||||||
vm: &'b mut Vm<'c>,
|
vm: &'b mut Vm<'c>,
|
||||||
instruction_data: InstructionData,
|
instruction_data: InstructionData,
|
||||||
@ -268,6 +278,7 @@ impl<'a> Vm<'a> {
|
|||||||
vm.set_register(a, register)
|
vm.set_register(a, register)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn multiply<'b, 'c>(
|
fn multiply<'b, 'c>(
|
||||||
vm: &'b mut Vm<'c>,
|
vm: &'b mut Vm<'c>,
|
||||||
instruction_data: InstructionData,
|
instruction_data: InstructionData,
|
||||||
@ -297,6 +308,7 @@ impl<'a> Vm<'a> {
|
|||||||
vm.set_register(a, register)
|
vm.set_register(a, register)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn divide<'b, 'c>(
|
fn divide<'b, 'c>(
|
||||||
vm: &'b mut Vm<'c>,
|
vm: &'b mut Vm<'c>,
|
||||||
instruction_data: InstructionData,
|
instruction_data: InstructionData,
|
||||||
@ -326,6 +338,7 @@ impl<'a> Vm<'a> {
|
|||||||
vm.set_register(a, register)
|
vm.set_register(a, register)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn modulo<'b, 'c>(
|
fn modulo<'b, 'c>(
|
||||||
vm: &'b mut Vm<'c>,
|
vm: &'b mut Vm<'c>,
|
||||||
instruction_data: InstructionData,
|
instruction_data: InstructionData,
|
||||||
@ -355,6 +368,7 @@ impl<'a> Vm<'a> {
|
|||||||
vm.set_register(a, register)
|
vm.set_register(a, register)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn test<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
|
fn test<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
|
||||||
let InstructionData {
|
let InstructionData {
|
||||||
b,
|
b,
|
||||||
@ -380,6 +394,7 @@ impl<'a> Vm<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn test_set<'b, 'c>(
|
fn test_set<'b, 'c>(
|
||||||
vm: &'b mut Vm<'c>,
|
vm: &'b mut Vm<'c>,
|
||||||
instruction_data: InstructionData,
|
instruction_data: InstructionData,
|
||||||
@ -418,6 +433,7 @@ impl<'a> Vm<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn equal<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
|
fn equal<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
|
||||||
let InstructionData {
|
let InstructionData {
|
||||||
a,
|
a,
|
||||||
@ -448,6 +464,7 @@ impl<'a> Vm<'a> {
|
|||||||
vm.set_register(a, register)
|
vm.set_register(a, register)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn less<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
|
fn less<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
|
||||||
let InstructionData {
|
let InstructionData {
|
||||||
a,
|
a,
|
||||||
@ -478,6 +495,7 @@ impl<'a> Vm<'a> {
|
|||||||
vm.set_register(a, register)
|
vm.set_register(a, register)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn less_equal<'b, 'c>(
|
fn less_equal<'b, 'c>(
|
||||||
vm: &'b mut Vm<'c>,
|
vm: &'b mut Vm<'c>,
|
||||||
instruction_data: InstructionData,
|
instruction_data: InstructionData,
|
||||||
@ -513,6 +531,7 @@ impl<'a> Vm<'a> {
|
|||||||
vm.set_register(a, register)
|
vm.set_register(a, register)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn negate<'b, 'c>(
|
fn negate<'b, 'c>(
|
||||||
vm: &'b mut Vm<'c>,
|
vm: &'b mut Vm<'c>,
|
||||||
instruction_data: InstructionData,
|
instruction_data: InstructionData,
|
||||||
@ -533,6 +552,7 @@ impl<'a> Vm<'a> {
|
|||||||
vm.set_register(a, register)
|
vm.set_register(a, register)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn not<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
|
fn not<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
|
||||||
let InstructionData {
|
let InstructionData {
|
||||||
a,
|
a,
|
||||||
@ -550,6 +570,7 @@ impl<'a> Vm<'a> {
|
|||||||
vm.set_register(a, register)
|
vm.set_register(a, register)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn jump<'b, 'c>(vm: &mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
|
fn jump<'b, 'c>(vm: &mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
|
||||||
let InstructionData { b, c, .. } = instruction_data;
|
let InstructionData { b, c, .. } = instruction_data;
|
||||||
let is_positive = c != 0;
|
let is_positive = c != 0;
|
||||||
@ -559,6 +580,7 @@ impl<'a> Vm<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn call<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
|
fn call<'b, 'c>(vm: &'b mut Vm<'c>, instruction_data: InstructionData) -> Result<(), VmError> {
|
||||||
let InstructionData {
|
let InstructionData {
|
||||||
a,
|
a,
|
||||||
@ -609,6 +631,7 @@ impl<'a> Vm<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
fn call_native<'b, 'c>(
|
fn call_native<'b, 'c>(
|
||||||
vm: &'b mut Vm<'c>,
|
vm: &'b mut Vm<'c>,
|
||||||
instruction_data: InstructionData,
|
instruction_data: InstructionData,
|
||||||
@ -665,7 +688,7 @@ impl<'a> Vm<'a> {
|
|||||||
instruction.disassembly_info()
|
instruction.disassembly_info()
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Operation::Return = operation {
|
if let Operation::RETURN = operation {
|
||||||
let should_return_value = instruction_data.b != 0;
|
let should_return_value = instruction_data.b != 0;
|
||||||
|
|
||||||
if !should_return_value {
|
if !should_return_value {
|
||||||
@ -684,7 +707,7 @@ impl<'a> Vm<'a> {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
let runner = RUNNERS[operation as usize];
|
let runner = RUNNERS[operation.0 as usize];
|
||||||
|
|
||||||
runner(self, instruction_data).unwrap();
|
runner(self, instruction_data).unwrap();
|
||||||
}
|
}
|
||||||
@ -1045,35 +1068,35 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
const ALL_OPERATIONS: [(Operation, Runner); 23] = [
|
const ALL_OPERATIONS: [(Operation, Runner); 23] = [
|
||||||
(Operation::Move, Vm::r#move),
|
(Operation::MOVE, Vm::r#move),
|
||||||
(Operation::Close, Vm::close),
|
(Operation::CLOSE, Vm::close),
|
||||||
(Operation::LoadBoolean, Vm::load_boolean),
|
(Operation::LOAD_BOOLEAN, Vm::load_boolean),
|
||||||
(Operation::LoadConstant, Vm::load_constant),
|
(Operation::LOAD_CONSTANT, Vm::load_constant),
|
||||||
(Operation::LoadList, Vm::load_list),
|
(Operation::LOAD_LIST, Vm::load_list),
|
||||||
(Operation::LoadSelf, Vm::load_self),
|
(Operation::LOAD_SELF, Vm::load_self),
|
||||||
(Operation::GetLocal, Vm::get_local),
|
(Operation::GET_LOCAL, Vm::get_local),
|
||||||
(Operation::SetLocal, Vm::set_local),
|
(Operation::SET_LOCAL, Vm::set_local),
|
||||||
(Operation::Add, Vm::add),
|
(Operation::ADD, Vm::add),
|
||||||
(Operation::Subtract, Vm::subtract),
|
(Operation::SUBTRACT, Vm::subtract),
|
||||||
(Operation::Multiply, Vm::multiply),
|
(Operation::MULTIPLY, Vm::multiply),
|
||||||
(Operation::Divide, Vm::divide),
|
(Operation::DIVIDE, Vm::divide),
|
||||||
(Operation::Modulo, Vm::modulo),
|
(Operation::MODULO, Vm::modulo),
|
||||||
(Operation::Test, Vm::test),
|
(Operation::TEST, Vm::test),
|
||||||
(Operation::TestSet, Vm::test_set),
|
(Operation::TEST_SET, Vm::test_set),
|
||||||
(Operation::Equal, Vm::equal),
|
(Operation::EQUAL, Vm::equal),
|
||||||
(Operation::Less, Vm::less),
|
(Operation::LESS, Vm::less),
|
||||||
(Operation::LessEqual, Vm::less_equal),
|
(Operation::LESS_EQUAL, Vm::less_equal),
|
||||||
(Operation::Negate, Vm::negate),
|
(Operation::NEGATE, Vm::negate),
|
||||||
(Operation::Not, Vm::not),
|
(Operation::NOT, Vm::not),
|
||||||
(Operation::Call, Vm::call),
|
(Operation::CALL, Vm::call),
|
||||||
(Operation::CallNative, Vm::call_native),
|
(Operation::CALL_NATIVE, Vm::call_native),
|
||||||
(Operation::Jump, Vm::jump),
|
(Operation::JUMP, Vm::jump),
|
||||||
];
|
];
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn operations_map_to_the_correct_runner() {
|
fn operations_map_to_the_correct_runner() {
|
||||||
for (operation, expected_runner) in ALL_OPERATIONS {
|
for (operation, expected_runner) in ALL_OPERATIONS {
|
||||||
let actual_runner = RUNNERS[operation as usize];
|
let actual_runner = RUNNERS[operation.0 as usize];
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
expected_runner, actual_runner,
|
expected_runner, actual_runner,
|
||||||
|
Loading…
Reference in New Issue
Block a user