blob: 3ec911dc6bf880de67f308a19fbe83fdae83a39a [file] [log] [blame]
// 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
},
]
);
}
}
}