| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use crate::compiler; |
| use crate::errors::UserError; |
| use crate::instruction::{DeviceProperty, RawAstLocation, RawCondition, RawInstruction, RawOp}; |
| use num_traits::FromPrimitive; |
| use std::collections::HashMap; |
| use std::fmt; |
| |
| // From <ddk/binding.h>. |
| const BIND_FLAGS: u32 = 0; |
| const BIND_PROTOCOL: u32 = 1; |
| const BIND_AUTOBIND: u32 = 2; |
| |
| #[derive(Debug, Clone, PartialEq)] |
| pub enum DebuggerError { |
| BindFlagsNotSupported, |
| InvalidCondition(u32), |
| IncorrectCondition, |
| InvalidOperation(u32), |
| InvalidAstLocation(u32), |
| IncorrectAstLocation, |
| MissingLabel, |
| MissingBindProtocol, |
| NoOutcome, |
| DuplicateKey(u32), |
| InvalidDeprecatedKey(u32), |
| } |
| |
| impl fmt::Display for DebuggerError { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| write!(f, "{}", UserError::from(self.clone())) |
| } |
| } |
| |
| type DevicePropertyMap = HashMap<u32, u32>; |
| |
| #[derive(Debug, PartialEq)] |
| enum DebuggerOutput { |
| ConditionStatement { key: u32, condition: RawCondition, success: bool, line: u32 }, |
| AbortStatement { line: u32 }, |
| AcceptStatementSuccess { key: u32, value: u32, line: u32 }, |
| AcceptStatementFailure { key: u32, line: u32 }, |
| IfCondition { key: u32, condition: RawCondition, success: bool, line: u32 }, |
| } |
| |
| pub fn debug( |
| instructions: &[RawInstruction<[u32; 3]>], |
| properties: &[DeviceProperty], |
| ) -> Result<bool, DebuggerError> { |
| let mut debugger = Debugger::new(instructions, properties)?; |
| let binds = debugger.evaluate_bind_program()?; |
| debugger.log_output()?; |
| Ok(binds) |
| } |
| |
| struct Debugger<'a> { |
| instructions: &'a [RawInstruction<[u32; 3]>], |
| properties: DevicePropertyMap, |
| output: Vec<DebuggerOutput>, |
| deprecated_key_identifiers: HashMap<u32, String>, |
| } |
| |
| impl<'a> Debugger<'a> { |
| pub fn new( |
| instructions: &'a [RawInstruction<[u32; 3]>], |
| properties: &[DeviceProperty], |
| ) -> Result<Self, DebuggerError> { |
| let properties = Debugger::construct_property_map(properties)?; |
| let output = Vec::new(); |
| let deprecated_key_identifiers = compiler::get_deprecated_key_identifiers(); |
| Ok(Debugger { instructions, properties, output, deprecated_key_identifiers }) |
| } |
| |
| fn construct_property_map( |
| properties: &[DeviceProperty], |
| ) -> Result<DevicePropertyMap, DebuggerError> { |
| let mut property_map = HashMap::new(); |
| for DeviceProperty { key, value } in properties { |
| if property_map.contains_key(key) { |
| return Err(DebuggerError::DuplicateKey(*key)); |
| } |
| property_map.insert(*key, *value); |
| } |
| Ok(property_map) |
| } |
| |
| fn key_string(&self, key: u32) -> Result<String, DebuggerError> { |
| match self.deprecated_key_identifiers.get(&key) { |
| Some(identifier) => Ok(format!("`{}` [{:#06x}]", identifier, key)), |
| None => Err(DebuggerError::InvalidDeprecatedKey(key)), |
| } |
| } |
| |
| fn get_device_property(&self, key: u32) -> Result<u32, DebuggerError> { |
| let value = self.properties.get(&key); |
| if let Some(value) = value { |
| return Ok(*value); |
| } |
| |
| // TODO(fxbug.dev/45663): The behavior of setting missing properties to 0 is implemented to be |
| // consistent with binding.cc. This behavior should eventually be changed to deal with |
| // missing properties in a better way. |
| match key { |
| BIND_PROTOCOL => return Err(DebuggerError::MissingBindProtocol), |
| BIND_AUTOBIND => { |
| println!( |
| "WARNING: Driver has BI_ABORT_IF_AUTOBIND. \ |
| This bind program will fail in autobind contexts." |
| ); |
| // Set autobind = false, since the debugger is always run with a specific driver. |
| Ok(0) |
| } |
| _ => { |
| println!( |
| "WARNING: Device has no value for {}. The value will be set to 0.", |
| self.key_string(key)? |
| ); |
| Ok(0) |
| } |
| } |
| } |
| |
| pub fn evaluate_bind_program(&mut self) -> Result<bool, DebuggerError> { |
| let mut instructions = self.instructions.iter(); |
| |
| while let Some(mut instruction) = instructions.next() { |
| let condition = FromPrimitive::from_u32(instruction.condition()) |
| .ok_or(DebuggerError::InvalidCondition(instruction.condition()))?; |
| let operation = FromPrimitive::from_u32(instruction.operation()) |
| .ok_or(DebuggerError::InvalidOperation(instruction.operation()))?; |
| |
| let condition_succeeds = if condition == RawCondition::Always { |
| true |
| } else { |
| let key = instruction.parameter_b(); |
| if key == BIND_FLAGS { |
| return Err(DebuggerError::BindFlagsNotSupported); |
| } |
| |
| let device_value = self.get_device_property(key)?; |
| |
| match condition { |
| RawCondition::Equal => device_value == instruction.value(), |
| RawCondition::NotEqual => device_value != instruction.value(), |
| RawCondition::Always => unreachable!(), |
| } |
| }; |
| |
| self.output_instruction(instruction, condition_succeeds)?; |
| |
| if !condition_succeeds { |
| continue; |
| } |
| |
| match operation { |
| RawOp::Abort => return Ok(false), |
| RawOp::Match => return Ok(true), |
| RawOp::Goto => { |
| let label = instruction.parameter_a(); |
| while !(FromPrimitive::from_u32(instruction.operation()) == Some(RawOp::Label) |
| && instruction.parameter_a() == label) |
| { |
| instruction = instructions.next().ok_or(DebuggerError::MissingLabel)?; |
| } |
| } |
| RawOp::Label => (), |
| } |
| } |
| |
| Err(DebuggerError::NoOutcome) |
| } |
| |
| fn output_instruction( |
| &mut self, |
| instruction: &RawInstruction<[u32; 3]>, |
| condition_succeeds: bool, |
| ) -> Result<(), DebuggerError> { |
| let condition = FromPrimitive::from_u32(instruction.condition()) |
| .ok_or(DebuggerError::InvalidCondition(instruction.condition()))?; |
| let operation = FromPrimitive::from_u32(instruction.operation()) |
| .ok_or(DebuggerError::InvalidOperation(instruction.operation()))?; |
| let ast_location = FromPrimitive::from_u32(instruction.ast_location()) |
| .ok_or(DebuggerError::InvalidAstLocation(instruction.ast_location()))?; |
| |
| match (operation, condition) { |
| (RawOp::Abort, RawCondition::Equal) | (RawOp::Abort, RawCondition::NotEqual) => { |
| if ast_location != RawAstLocation::ConditionStatement { |
| return Err(DebuggerError::IncorrectAstLocation); |
| } |
| // The abort instruction came from a condition statement. An equality condition |
| // statement is compiled to AbortIfNotEqual and vice versa, so the condition needs |
| // to be flipped. Also, the condition was successful if the instruction doesn't |
| // abort, so the `success` value needs to be flipped. |
| self.output.push(DebuggerOutput::ConditionStatement { |
| key: instruction.parameter_b(), |
| condition: match condition { |
| RawCondition::Equal => RawCondition::NotEqual, |
| RawCondition::NotEqual => RawCondition::Equal, |
| _ => return Err(DebuggerError::IncorrectCondition), |
| }, |
| success: !condition_succeeds, |
| line: instruction.line(), |
| }); |
| } |
| (RawOp::Abort, RawCondition::Always) => match ast_location { |
| RawAstLocation::AcceptStatementFailure => { |
| // The abort instruction came from the end of an accept statement, meaning that |
| // the accept statement failed. |
| self.output.push(DebuggerOutput::AcceptStatementFailure { |
| key: instruction.extra(), |
| line: instruction.line(), |
| }) |
| } |
| RawAstLocation::AbortStatement => { |
| self.output.push(DebuggerOutput::AbortStatement { line: instruction.line() }) |
| } |
| _ => return Err(DebuggerError::IncorrectAstLocation), |
| }, |
| (RawOp::Goto, RawCondition::Equal) => match ast_location { |
| RawAstLocation::AcceptStatementValue => { |
| if condition_succeeds { |
| // The goto instruciton came from one of the values in an accept statement. |
| // The fact that the jump succeeded means that the device had this value, so |
| // the accept statement was satisfied. |
| self.output.push(DebuggerOutput::AcceptStatementSuccess { |
| key: instruction.parameter_b(), |
| value: instruction.value(), |
| line: instruction.line(), |
| }); |
| } |
| } |
| RawAstLocation::IfCondition => { |
| // The goto instruction came from an inequality if statement condition. The |
| // condition was satisfied if the jump doesn't succeed, so the `success` value |
| // needs to be flipped. |
| self.output.push(DebuggerOutput::IfCondition { |
| key: instruction.parameter_b(), |
| condition: RawCondition::NotEqual, |
| success: !condition_succeeds, |
| line: instruction.line(), |
| }) |
| } |
| _ => return Err(DebuggerError::IncorrectAstLocation), |
| }, |
| (RawOp::Goto, RawCondition::NotEqual) => { |
| if ast_location != RawAstLocation::IfCondition { |
| return Err(DebuggerError::IncorrectAstLocation); |
| } |
| // The goto instruction came from an equality if statement condition. The |
| // condition was satisfied if the jump doesn't succeed, so the `success` value |
| // needs to be flipped. |
| self.output.push(DebuggerOutput::IfCondition { |
| key: instruction.parameter_b(), |
| condition: RawCondition::Equal, |
| success: !condition_succeeds, |
| line: instruction.line(), |
| }); |
| } |
| _ => (), |
| } |
| Ok(()) |
| } |
| |
| fn log_output(&self) -> Result<(), DebuggerError> { |
| for output in &self.output { |
| match output { |
| DebuggerOutput::ConditionStatement { key, condition, success, line } => { |
| self.log_condition_statement(*key, *condition, *success, *line)?; |
| } |
| DebuggerOutput::AbortStatement { line } => self.log_abort_statement(*line), |
| DebuggerOutput::AcceptStatementSuccess { key, value, line } => { |
| self.log_accept_statement_success(*key, *value, *line)? |
| } |
| DebuggerOutput::AcceptStatementFailure { key, line } => { |
| self.log_accept_statement_failure(*key, *line)? |
| } |
| DebuggerOutput::IfCondition { key, condition, success, line } => { |
| self.log_if_condition(*key, *condition, *success, *line)?; |
| } |
| } |
| } |
| Ok(()) |
| } |
| |
| fn log_condition_statement( |
| &self, |
| key: u32, |
| condition: RawCondition, |
| success: bool, |
| line: u32, |
| ) -> Result<(), DebuggerError> { |
| let outcome_string = if success { "succeeded" } else { "failed" }; |
| println!("Line {}: Condition statement {}.", line, outcome_string); |
| |
| if condition_needs_actual_value(success, condition)? { |
| println!("\t{}", self.actual_value_string(key)?) |
| } |
| |
| Ok(()) |
| } |
| |
| fn log_abort_statement(&self, line: u32) { |
| println!("Line {}: Abort statement reached.", line); |
| } |
| |
| fn log_accept_statement_success( |
| &self, |
| key: u32, |
| value: u32, |
| line: u32, |
| ) -> Result<(), DebuggerError> { |
| println!( |
| "Line {}: Accept statement succeeded.\n\tThe value of {} was {:#010x}.", |
| line, |
| self.key_string(key)?, |
| value |
| ); |
| |
| Ok(()) |
| } |
| |
| fn log_accept_statement_failure(&self, key: u32, line: u32) -> Result<(), DebuggerError> { |
| println!("Line {}: Accept statement failed.\n\t{}", line, self.actual_value_string(key)?); |
| Ok(()) |
| } |
| |
| fn log_if_condition( |
| &self, |
| key: u32, |
| condition: RawCondition, |
| success: bool, |
| line: u32, |
| ) -> Result<(), DebuggerError> { |
| let outcome_string = if success { "succeeded" } else { "failed" }; |
| println!("Line {}: If statement condition {}.", line, outcome_string); |
| |
| if condition_needs_actual_value(success, condition)? { |
| println!("\t{}", self.actual_value_string(key)?) |
| } |
| |
| Ok(()) |
| } |
| |
| fn actual_value_string(&self, key: u32) -> Result<String, DebuggerError> { |
| let value = self.properties.get(&key); |
| |
| Ok(match value { |
| Some(value) => { |
| format!("Actual value of {} was {:#010x}.", self.key_string(key)?, value) |
| } |
| None => format!("Device had no value for {}.", self.key_string(key)?), |
| }) |
| } |
| } |
| |
| fn condition_needs_actual_value( |
| success: bool, |
| condition: RawCondition, |
| ) -> Result<bool, DebuggerError> { |
| match condition { |
| RawCondition::Equal => Ok(!success), |
| RawCondition::NotEqual => Ok(success), |
| RawCondition::Always => Err(DebuggerError::IncorrectCondition), |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use crate::bind_program::{Condition, ConditionOp, Statement}; |
| use crate::compiler::{self, Symbol, SymbolTable}; |
| use crate::instruction::{self, Instruction}; |
| use crate::make_identifier; |
| use crate::parser_common::{CompoundIdentifier, Span, Value}; |
| |
| fn compile_to_raw( |
| statements: Vec<Statement>, |
| symbol_table: &SymbolTable, |
| ) -> Vec<RawInstruction<[u32; 3]>> { |
| compiler::compile_statements(statements, symbol_table) |
| .unwrap() |
| .into_iter() |
| .map(|symbolic| symbolic.to_instruction().to_raw()) |
| .collect() |
| } |
| |
| fn span_with_line(line: u32) -> Span<'static> { |
| let mut span = Span::new(); |
| span.line = line; |
| span |
| } |
| |
| #[test] |
| fn autobind() { |
| // Autobind is false (BIND_AUTOBIND has the value 0). |
| let instructions = vec![ |
| Instruction::Match(instruction::Condition::Equal(BIND_AUTOBIND, 0)).to_raw(), |
| Instruction::Abort(instruction::Condition::Always).to_raw(), |
| ]; |
| let properties = Vec::new(); |
| assert_eq!(debug(&instructions, &properties), Ok(true)); |
| } |
| |
| #[test] |
| fn bind_flags_not_supported() { |
| let instructions = |
| vec![Instruction::Abort(instruction::Condition::Equal(BIND_FLAGS, 0)).to_raw()]; |
| let properties = Vec::new(); |
| assert_eq!(debug(&instructions, &properties), Err(DebuggerError::BindFlagsNotSupported)); |
| } |
| |
| #[test] |
| fn missing_bind_protocol() { |
| let instructions = |
| vec![Instruction::Abort(instruction::Condition::Equal(BIND_PROTOCOL, 5)).to_raw()]; |
| let properties = Vec::new(); |
| assert_eq!(debug(&instructions, &properties), Err(DebuggerError::MissingBindProtocol)); |
| } |
| |
| #[test] |
| fn duplicate_key() { |
| let instructions = Vec::new(); |
| let properties = vec![ |
| DeviceProperty { key: 0x0100, value: 42 }, |
| DeviceProperty { key: 0x0100, value: 5 }, |
| ]; |
| assert_eq!(debug(&instructions, &properties), Err(DebuggerError::DuplicateKey(0x0100))); |
| } |
| |
| #[test] |
| fn default_value_zero() { |
| // When the device doesn't have the property, its value is set to 0. |
| let statements = vec![Statement::ConditionStatement { |
| span: Span::new(), |
| condition: Condition { |
| span: Span::new(), |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(0), |
| }, |
| }]; |
| let mut symbol_table = HashMap::new(); |
| symbol_table.insert(make_identifier!("abc"), Symbol::DeprecatedKey(0x0100)); |
| let raw_instructions = compile_to_raw(statements, &symbol_table); |
| let properties = Vec::new(); |
| assert_eq!(debug(&raw_instructions, &properties), Ok(true)); |
| } |
| |
| mod condition_equals { |
| use super::*; |
| |
| fn condition_equals_instructions() -> Vec<RawInstruction<[u32; 3]>> { |
| let statements = vec![Statement::ConditionStatement { |
| span: span_with_line(7), |
| condition: Condition { |
| span: span_with_line(7), |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(42), |
| }, |
| }]; |
| let mut symbol_table = HashMap::new(); |
| symbol_table.insert(make_identifier!("abc"), Symbol::DeprecatedKey(0x0100)); |
| compile_to_raw(statements, &symbol_table) |
| } |
| |
| #[test] |
| fn correct_value() { |
| // Binds when the device has the correct value for the property. |
| let raw_instructions = condition_equals_instructions(); |
| let properties = vec![DeviceProperty { key: 0x0100, value: 42 }]; |
| let mut debugger = Debugger::new(&raw_instructions, &properties).unwrap(); |
| assert!(debugger.evaluate_bind_program().unwrap()); |
| assert_eq!( |
| debugger.output, |
| vec![DebuggerOutput::ConditionStatement { |
| key: 0x0100, |
| condition: RawCondition::Equal, |
| success: true, |
| line: 7 |
| }] |
| ); |
| } |
| |
| #[test] |
| fn wrong_value() { |
| // Doesn't bind when the device has the wrong value for the property. |
| let raw_instructions = condition_equals_instructions(); |
| let properties = vec![DeviceProperty { key: 0x0100, value: 5 }]; |
| let mut debugger = Debugger::new(&raw_instructions, &properties).unwrap(); |
| assert!(!debugger.evaluate_bind_program().unwrap()); |
| assert_eq!( |
| debugger.output, |
| vec![DebuggerOutput::ConditionStatement { |
| key: 0x0100, |
| condition: RawCondition::Equal, |
| success: false, |
| line: 7 |
| }] |
| ); |
| } |
| |
| #[test] |
| fn missing_value() { |
| // Doesn't bind when the property is not present in the device. |
| let raw_instructions = condition_equals_instructions(); |
| let properties = Vec::new(); |
| let mut debugger = Debugger::new(&raw_instructions, &properties).unwrap(); |
| assert!(!debugger.evaluate_bind_program().unwrap()); |
| assert_eq!( |
| debugger.output, |
| vec![DebuggerOutput::ConditionStatement { |
| key: 0x0100, |
| condition: RawCondition::Equal, |
| success: false, |
| line: 7 |
| }] |
| ); |
| } |
| } |
| |
| mod condition_not_equals { |
| use super::*; |
| |
| fn condition_not_equals_instructions() -> Vec<RawInstruction<[u32; 3]>> { |
| let statements = vec![Statement::ConditionStatement { |
| span: span_with_line(7), |
| condition: Condition { |
| span: span_with_line(7), |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::NotEquals, |
| rhs: Value::NumericLiteral(42), |
| }, |
| }]; |
| let mut symbol_table = HashMap::new(); |
| symbol_table.insert(make_identifier!("abc"), Symbol::DeprecatedKey(0x0100)); |
| compile_to_raw(statements, &symbol_table) |
| } |
| |
| #[test] |
| fn different_value() { |
| // Binds when the device has a different value for the property. |
| let raw_instructions = condition_not_equals_instructions(); |
| let properties = vec![DeviceProperty { key: 0x0100, value: 5 }]; |
| let mut debugger = Debugger::new(&raw_instructions, &properties).unwrap(); |
| assert!(debugger.evaluate_bind_program().unwrap()); |
| assert_eq!( |
| debugger.output, |
| vec![DebuggerOutput::ConditionStatement { |
| key: 0x0100, |
| condition: RawCondition::NotEqual, |
| success: true, |
| line: 7 |
| }] |
| ); |
| } |
| |
| #[test] |
| fn missing_value() { |
| // Binds when the property is not present in the device. |
| let raw_instructions = condition_not_equals_instructions(); |
| let properties = Vec::new(); |
| let mut debugger = Debugger::new(&raw_instructions, &properties).unwrap(); |
| assert!(debugger.evaluate_bind_program().unwrap()); |
| assert_eq!( |
| debugger.output, |
| vec![DebuggerOutput::ConditionStatement { |
| key: 0x0100, |
| condition: RawCondition::NotEqual, |
| success: true, |
| line: 7 |
| }] |
| ); |
| } |
| |
| #[test] |
| fn same_value() { |
| // Doesn't bind when the device has the property in the condition statement. |
| let raw_instructions = condition_not_equals_instructions(); |
| let properties = vec![DeviceProperty { key: 0x0100, value: 42 }]; |
| let mut debugger = Debugger::new(&raw_instructions, &properties).unwrap(); |
| assert!(!debugger.evaluate_bind_program().unwrap()); |
| assert_eq!( |
| debugger.output, |
| vec![DebuggerOutput::ConditionStatement { |
| key: 0x0100, |
| condition: RawCondition::NotEqual, |
| success: false, |
| line: 7 |
| }] |
| ); |
| } |
| } |
| |
| mod accept { |
| use super::*; |
| |
| fn accept_instructions() -> Vec<RawInstruction<[u32; 3]>> { |
| let statements = vec![Statement::Accept { |
| span: span_with_line(7), |
| identifier: make_identifier!("abc"), |
| values: vec![Value::NumericLiteral(42), Value::NumericLiteral(314)], |
| }]; |
| let mut symbol_table = HashMap::new(); |
| symbol_table.insert(make_identifier!("abc"), Symbol::DeprecatedKey(0x0100)); |
| |
| compile_to_raw(statements, &symbol_table) |
| } |
| |
| #[test] |
| fn accepted_value() { |
| // Binds when the device has one of the accepted values for the property. |
| let raw_instructions = accept_instructions(); |
| let properties = vec![DeviceProperty { key: 0x0100, value: 42 }]; |
| let mut debugger = Debugger::new(&raw_instructions, &properties).unwrap(); |
| assert!(debugger.evaluate_bind_program().unwrap()); |
| assert_eq!( |
| debugger.output, |
| vec![DebuggerOutput::AcceptStatementSuccess { key: 0x0100, value: 42, line: 7 }] |
| ); |
| } |
| |
| #[test] |
| fn different_value() { |
| // Doesn't bind when the device has a different value for the property. |
| let raw_instructions = accept_instructions(); |
| let properties = vec![DeviceProperty { key: 0x0100, value: 5 }]; |
| let mut debugger = Debugger::new(&raw_instructions, &properties).unwrap(); |
| assert!(!debugger.evaluate_bind_program().unwrap()); |
| assert_eq!( |
| debugger.output, |
| vec![DebuggerOutput::AcceptStatementFailure { key: 0x0100, line: 7 }] |
| ); |
| } |
| |
| #[test] |
| fn missing_value() { |
| // Doesn't bind when the device is missing the property. |
| let raw_instructions = accept_instructions(); |
| let properties = Vec::new(); |
| let mut debugger = Debugger::new(&raw_instructions, &properties).unwrap(); |
| assert!(!debugger.evaluate_bind_program().unwrap()); |
| assert_eq!( |
| debugger.output, |
| vec![DebuggerOutput::AcceptStatementFailure { key: 0x0100, line: 7 }] |
| ); |
| } |
| } |
| |
| mod if_else { |
| use super::*; |
| |
| fn if_else_instructions() -> Vec<RawInstruction<[u32; 3]>> { |
| /* |
| if abc == 1 { |
| xyz == 1; |
| } else if abc == 2{ |
| xyz == 2; |
| } else { |
| xyz == 3; |
| } |
| */ |
| |
| let condition1 = Condition { |
| span: span_with_line(1), |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(1), |
| }; |
| let statement1 = Statement::ConditionStatement { |
| span: span_with_line(2), |
| condition: Condition { |
| span: span_with_line(2), |
| lhs: make_identifier!("xyz"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(1), |
| }, |
| }; |
| let condition2 = Condition { |
| span: span_with_line(3), |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(2), |
| }; |
| let statement2 = Statement::ConditionStatement { |
| span: span_with_line(4), |
| condition: Condition { |
| span: span_with_line(1), |
| lhs: make_identifier!("xyz"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(2), |
| }, |
| }; |
| let statement3 = Statement::ConditionStatement { |
| span: span_with_line(6), |
| condition: Condition { |
| span: span_with_line(6), |
| lhs: make_identifier!("xyz"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(3), |
| }, |
| }; |
| |
| let statements = vec![Statement::If { |
| span: span_with_line(1), |
| blocks: vec![ |
| (condition1.clone(), vec![statement1.clone()]), |
| (condition2.clone(), vec![statement2.clone()]), |
| ], |
| else_block: vec![statement3.clone()], |
| }]; |
| let mut symbol_table = HashMap::new(); |
| symbol_table.insert(make_identifier!("abc"), Symbol::DeprecatedKey(0x0100)); |
| symbol_table.insert(make_identifier!("xyz"), Symbol::DeprecatedKey(0x0200)); |
| |
| compile_to_raw(statements, &symbol_table) |
| } |
| |
| #[test] |
| fn if_clause_satisfied() { |
| // Binds when the if clause is satisfied. |
| let raw_instructions = if_else_instructions(); |
| let properties = vec![ |
| DeviceProperty { key: 0x0100, value: 1 }, |
| DeviceProperty { key: 0x0200, value: 1 }, |
| ]; |
| let mut debugger = Debugger::new(&raw_instructions, &properties).unwrap(); |
| assert!(debugger.evaluate_bind_program().unwrap()); |
| assert_eq!( |
| debugger.output, |
| vec![ |
| DebuggerOutput::IfCondition { |
| key: 0x0100, |
| condition: RawCondition::Equal, |
| success: true, |
| line: 1 |
| }, |
| DebuggerOutput::ConditionStatement { |
| key: 0x0200, |
| condition: RawCondition::Equal, |
| success: true, |
| line: 2 |
| } |
| ] |
| ); |
| } |
| |
| #[test] |
| fn if_else_clause_satisfied() { |
| // Binds when the if else clause is satisfied. |
| let raw_instructions = if_else_instructions(); |
| let properties = vec![ |
| DeviceProperty { key: 0x0100, value: 2 }, |
| DeviceProperty { key: 0x0200, value: 2 }, |
| ]; |
| let mut debugger = Debugger::new(&raw_instructions, &properties).unwrap(); |
| assert!(debugger.evaluate_bind_program().unwrap()); |
| assert_eq!( |
| debugger.output, |
| vec![ |
| DebuggerOutput::IfCondition { |
| key: 0x0100, |
| condition: RawCondition::Equal, |
| success: false, |
| line: 1 |
| }, |
| DebuggerOutput::IfCondition { |
| key: 0x0100, |
| condition: RawCondition::Equal, |
| success: true, |
| line: 3 |
| }, |
| DebuggerOutput::ConditionStatement { |
| key: 0x0200, |
| condition: RawCondition::Equal, |
| success: true, |
| line: 4 |
| } |
| ] |
| ); |
| } |
| |
| #[test] |
| fn else_clause_satisfied() { |
| // Binds when the else clause is satisfied. |
| let raw_instructions = if_else_instructions(); |
| let properties = vec![DeviceProperty { key: 0x0200, value: 3 }]; |
| let mut debugger = Debugger::new(&raw_instructions, &properties).unwrap(); |
| assert!(debugger.evaluate_bind_program().unwrap()); |
| assert_eq!( |
| debugger.output, |
| vec![ |
| DebuggerOutput::IfCondition { |
| key: 0x0100, |
| condition: RawCondition::Equal, |
| success: false, |
| line: 1 |
| }, |
| DebuggerOutput::IfCondition { |
| key: 0x0100, |
| condition: RawCondition::Equal, |
| success: false, |
| line: 3 |
| }, |
| DebuggerOutput::ConditionStatement { |
| key: 0x0200, |
| condition: RawCondition::Equal, |
| success: true, |
| line: 6 |
| } |
| ] |
| ); |
| } |
| |
| #[test] |
| fn incorrect_values() { |
| // Doesn't bind when the device has incorrect values for the properties. |
| let raw_instructions = if_else_instructions(); |
| let properties = vec![ |
| DeviceProperty { key: 0x0100, value: 42 }, |
| DeviceProperty { key: 0x0200, value: 42 }, |
| ]; |
| let mut debugger = Debugger::new(&raw_instructions, &properties).unwrap(); |
| assert!(!debugger.evaluate_bind_program().unwrap()); |
| assert_eq!( |
| debugger.output, |
| vec![ |
| DebuggerOutput::IfCondition { |
| key: 0x0100, |
| condition: RawCondition::Equal, |
| success: false, |
| line: 1 |
| }, |
| DebuggerOutput::IfCondition { |
| key: 0x0100, |
| condition: RawCondition::Equal, |
| success: false, |
| line: 3 |
| }, |
| DebuggerOutput::ConditionStatement { |
| key: 0x0200, |
| condition: RawCondition::Equal, |
| success: false, |
| line: 6 |
| } |
| ] |
| ); |
| } |
| |
| #[test] |
| fn missing_values() { |
| // Doesn't bind when the properties are missing in the device. |
| let raw_instructions = if_else_instructions(); |
| let properties = Vec::new(); |
| let mut debugger = Debugger::new(&raw_instructions, &properties).unwrap(); |
| assert!(!debugger.evaluate_bind_program().unwrap()); |
| assert_eq!( |
| debugger.output, |
| vec![ |
| DebuggerOutput::IfCondition { |
| key: 0x0100, |
| condition: RawCondition::Equal, |
| success: false, |
| line: 1 |
| }, |
| DebuggerOutput::IfCondition { |
| key: 0x0100, |
| condition: RawCondition::Equal, |
| success: false, |
| line: 3 |
| }, |
| DebuggerOutput::ConditionStatement { |
| key: 0x0200, |
| condition: RawCondition::Equal, |
| success: false, |
| line: 6 |
| } |
| ] |
| ); |
| } |
| } |
| |
| mod abort { |
| use super::*; |
| |
| fn abort_instructions() -> Vec<RawInstruction<[u32; 3]>> { |
| let statements = vec![ |
| Statement::ConditionStatement { |
| span: span_with_line(7), |
| condition: Condition { |
| span: span_with_line(7), |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(42), |
| }, |
| }, |
| Statement::Abort { span: span_with_line(8) }, |
| ]; |
| let mut symbol_table = HashMap::new(); |
| symbol_table.insert(make_identifier!("abc"), Symbol::DeprecatedKey(0x0100)); |
| |
| compile_to_raw(statements, &symbol_table) |
| } |
| |
| #[test] |
| fn aborts() { |
| // Doesn't bind when abort statement is present. |
| let raw_instructions = abort_instructions(); |
| let properties = vec![ |
| DeviceProperty { key: 0x0100, value: 42 }, |
| DeviceProperty { key: 0x0200, value: 1 }, |
| ]; |
| let mut debugger = Debugger::new(&raw_instructions, &properties).unwrap(); |
| assert!(!debugger.evaluate_bind_program().unwrap()); |
| assert_eq!( |
| debugger.output, |
| vec![ |
| DebuggerOutput::ConditionStatement { |
| key: 0x0100, |
| condition: RawCondition::Equal, |
| success: true, |
| line: 7 |
| }, |
| DebuggerOutput::AbortStatement { line: 8 }, |
| ] |
| ); |
| } |
| } |
| |
| mod full_program { |
| use super::*; |
| |
| fn full_program_instructions() -> Vec<RawInstruction<[u32; 3]>> { |
| /* |
| if abc == 42 { |
| abort; |
| } else { |
| accept xyz {1, 2}; |
| pqr != 5; |
| } |
| */ |
| |
| let condition = Condition { |
| span: span_with_line(1), |
| lhs: make_identifier!("abc"), |
| op: ConditionOp::Equals, |
| rhs: Value::NumericLiteral(42), |
| }; |
| let abort_statement = Statement::Abort { span: span_with_line(2) }; |
| let accept_statement = Statement::Accept { |
| span: span_with_line(4), |
| identifier: make_identifier!("xyz"), |
| values: vec![Value::NumericLiteral(1), Value::NumericLiteral(2)], |
| }; |
| let condition_statement = Statement::ConditionStatement { |
| span: span_with_line(5), |
| condition: Condition { |
| span: span_with_line(5), |
| lhs: make_identifier!("pqr"), |
| op: ConditionOp::NotEquals, |
| rhs: Value::NumericLiteral(5), |
| }, |
| }; |
| |
| let statements = vec![Statement::If { |
| span: span_with_line(1), |
| blocks: vec![(condition, vec![abort_statement])], |
| else_block: vec![accept_statement, condition_statement], |
| }]; |
| let mut symbol_table = HashMap::new(); |
| symbol_table.insert(make_identifier!("abc"), Symbol::DeprecatedKey(0x0100)); |
| symbol_table.insert(make_identifier!("xyz"), Symbol::DeprecatedKey(0x0200)); |
| symbol_table.insert(make_identifier!("pqr"), Symbol::DeprecatedKey(0x0300)); |
| |
| compile_to_raw(statements, &symbol_table) |
| } |
| |
| #[test] |
| fn if_condition_satisfied() { |
| // Aborts because if condition is true. |
| let raw_instructions = full_program_instructions(); |
| let properties = vec![DeviceProperty { key: 0x0100, value: 42 }]; |
| let mut debugger = Debugger::new(&raw_instructions, &properties).unwrap(); |
| assert!(!debugger.evaluate_bind_program().unwrap()); |
| assert_eq!( |
| debugger.output, |
| vec![ |
| DebuggerOutput::IfCondition { |
| key: 0x0100, |
| condition: RawCondition::Equal, |
| success: true, |
| line: 1 |
| }, |
| DebuggerOutput::AbortStatement { line: 2 }, |
| ] |
| ); |
| } |
| |
| #[test] |
| fn else_block_satisfied() { |
| // Binds because all statements inside else block are satisfied. |
| let raw_instructions = full_program_instructions(); |
| let properties = vec![ |
| DeviceProperty { key: 0x0100, value: 43 }, |
| DeviceProperty { key: 0x0200, value: 1 }, |
| ]; |
| let mut debugger = Debugger::new(&raw_instructions, &properties).unwrap(); |
| assert!(debugger.evaluate_bind_program().unwrap()); |
| assert_eq!( |
| debugger.output, |
| vec![ |
| DebuggerOutput::IfCondition { |
| key: 0x0100, |
| condition: RawCondition::Equal, |
| success: false, |
| line: 1 |
| }, |
| DebuggerOutput::AcceptStatementSuccess { key: 0x0200, value: 1, line: 4 }, |
| DebuggerOutput::ConditionStatement { |
| key: 0x0300, |
| condition: RawCondition::NotEqual, |
| success: true, |
| line: 5 |
| }, |
| ] |
| ); |
| } |
| |
| #[test] |
| fn accept_statement_not_satisfied() { |
| // Doesn't bind because accept statement is not satisfied. |
| let raw_instructions = full_program_instructions(); |
| let properties = vec![ |
| DeviceProperty { key: 0x0100, value: 43 }, |
| DeviceProperty { key: 0x0200, value: 3 }, |
| ]; |
| let mut debugger = Debugger::new(&raw_instructions, &properties).unwrap(); |
| assert!(!debugger.evaluate_bind_program().unwrap()); |
| assert_eq!( |
| debugger.output, |
| vec![ |
| DebuggerOutput::IfCondition { |
| key: 0x0100, |
| condition: RawCondition::Equal, |
| success: false, |
| line: 1 |
| }, |
| DebuggerOutput::AcceptStatementFailure { key: 0x0200, line: 4 }, |
| ] |
| ); |
| } |
| |
| #[test] |
| fn condition_statement_not_satisfied() { |
| // Doesn't bind because condition statement is not satisfied. |
| let raw_instructions = full_program_instructions(); |
| let properties = vec![ |
| DeviceProperty { key: 0x0100, value: 43 }, |
| DeviceProperty { key: 0x0200, value: 1 }, |
| DeviceProperty { key: 0x0300, value: 5 }, |
| ]; |
| let mut debugger = Debugger::new(&raw_instructions, &properties).unwrap(); |
| assert!(!debugger.evaluate_bind_program().unwrap()); |
| assert_eq!( |
| debugger.output, |
| vec![ |
| DebuggerOutput::IfCondition { |
| key: 0x0100, |
| condition: RawCondition::Equal, |
| success: false, |
| line: 1 |
| }, |
| DebuggerOutput::AcceptStatementSuccess { key: 0x0200, value: 1, line: 4 }, |
| DebuggerOutput::ConditionStatement { |
| key: 0x0300, |
| condition: RawCondition::NotEqual, |
| success: false, |
| line: 5 |
| }, |
| ] |
| ); |
| } |
| } |
| } |