blob: 93a2063391e6a44ad3e54c9113c5e067486b2501 [file] [log] [blame]
// Copyright 2019 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::bind_program::{Condition, ConditionOp, Statement};
use crate::compiler::{self, Symbol, SymbolTable, SymbolicInstruction, SymbolicInstructionInfo};
use crate::device_specification::{DeviceSpecification, Property};
use crate::errors::UserError;
use crate::instruction::{InstructionDebug, RawAstLocation};
use crate::parser_common::{self, CompoundIdentifier, Span, Value};
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::str::FromStr;
use thiserror::Error;
#[derive(Debug, Error, Clone, PartialEq)]
pub enum DebuggerError {
BindParserError(parser_common::BindParserError),
CompilerError(compiler::CompilerError),
DuplicateKey(CompoundIdentifier),
MissingLabel,
NoOutcome,
IncorrectAstLocation,
InvalidAstLocation,
UnknownKey(CompoundIdentifier),
InvalidValueSymbol(Symbol),
}
impl fmt::Display for DebuggerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", UserError::from(self.clone()))
}
}
type DevicePropertyMap = HashMap<Symbol, DeviceValue>;
#[derive(Debug, PartialEq)]
struct DeviceValue {
symbol: Option<Symbol>,
identifier: Option<CompoundIdentifier>,
}
#[derive(Debug, PartialEq)]
pub enum AstLocation<'a> {
ConditionStatement(Statement<'a>),
AcceptStatementValue { identifier: CompoundIdentifier, value: Value, span: Span<'a> },
AcceptStatementFailure { identifier: CompoundIdentifier, symbol: Symbol, span: Span<'a> },
IfCondition(Condition<'a>),
AbortStatement(Statement<'a>),
}
impl<'a> AstLocation<'a> {
pub fn to_instruction_debug(self) -> InstructionDebug {
match self {
AstLocation::ConditionStatement(statement) => InstructionDebug {
line: statement.get_span().line,
ast_location: RawAstLocation::ConditionStatement,
extra: 0,
},
AstLocation::AcceptStatementValue { span, .. } => InstructionDebug {
line: span.line,
ast_location: RawAstLocation::AcceptStatementValue,
extra: 0,
},
AstLocation::AcceptStatementFailure { span, symbol, .. } => InstructionDebug {
line: span.line,
ast_location: RawAstLocation::AcceptStatementFailure,
extra: symbol.to_bytecode(),
},
AstLocation::IfCondition(condition) => InstructionDebug {
line: condition.span.line,
ast_location: RawAstLocation::IfCondition,
extra: 0,
},
AstLocation::AbortStatement(statement) => InstructionDebug {
line: statement.get_span().line,
ast_location: RawAstLocation::AbortStatement,
extra: 0,
},
}
}
}
#[derive(Debug, PartialEq)]
enum DebuggerOutput<'a> {
ConditionStatement {
statement: &'a Statement<'a>,
success: bool,
},
AbortStatement {
statement: &'a Statement<'a>,
},
AcceptStatementSuccess {
identifier: &'a CompoundIdentifier,
value: &'a Value,
value_symbol: &'a Symbol,
span: &'a Span<'a>,
},
AcceptStatementFailure {
identifier: &'a CompoundIdentifier,
span: &'a Span<'a>,
},
IfCondition {
condition: &'a Condition<'a>,
success: bool,
},
}
pub fn debug_from_str<'a>(
instructions: &[SymbolicInstructionInfo<'a>],
symbol_table: &SymbolTable,
device_file: &str,
) -> Result<bool, DebuggerError> {
let device_specification =
DeviceSpecification::from_str(device_file).map_err(DebuggerError::BindParserError)?;
debug_from_device_specification(instructions, symbol_table, device_specification)
}
pub fn debug_from_device_specification<'a>(
instructions: &[SymbolicInstructionInfo<'a>],
symbol_table: &SymbolTable,
device_specification: DeviceSpecification,
) -> Result<bool, DebuggerError> {
let mut debugger = Debugger::new(&device_specification.properties, instructions, symbol_table)?;
let binds = debugger.evaluate_bind_program()?;
debugger.log_output()?;
Ok(binds)
}
struct Debugger<'a> {
device_properties: DevicePropertyMap,
instructions: &'a [SymbolicInstructionInfo<'a>],
symbol_table: &'a SymbolTable,
output: Vec<DebuggerOutput<'a>>,
}
impl<'a> Debugger<'a> {
fn new(
properties: &[Property],
instructions: &'a [SymbolicInstructionInfo<'a>],
symbol_table: &'a SymbolTable,
) -> Result<Self, DebuggerError> {
let device_properties = Debugger::construct_property_map(properties, symbol_table)?;
let output = Vec::new();
Ok(Debugger { device_properties, instructions, symbol_table, output })
}
/// Constructs a map of the device's properties. The keys are of type Symbol, and only keys
/// which appear in the bind program's symbol table are included. Any other keys in the device
/// specification can be safely ignored, since the bind program won't check their values. The
/// values are a struct containing both a symbol and an identifier, to allow the debugger to
/// output both the identifier and the literal value it corresponds to when printing values.
/// Both these fields are optional. If the symbol is None, then the device value is an
/// identifier not defined in the symbol table. If the identifier is None, then the device
/// value is a literal.
fn construct_property_map(
properties: &[Property],
symbol_table: &SymbolTable,
) -> Result<DevicePropertyMap, DebuggerError> {
let mut property_map = HashMap::new();
let mut keys_seen = HashSet::new();
for Property { key, value } in properties {
// Check for duplicate keys in the device specification. This is done using a separate
// set since the property_map only contains keys which are present in the symbol table.
if keys_seen.contains(key) {
return Err(DebuggerError::DuplicateKey(key.clone()));
}
keys_seen.insert(key.clone());
if let Some(key_symbol) = symbol_table.get(key) {
let device_value = match value {
Value::NumericLiteral(n) => {
DeviceValue { symbol: Some(Symbol::NumberValue(*n)), identifier: None }
}
Value::StringLiteral(s) => DeviceValue {
symbol: Some(Symbol::StringValue(s.to_string())),
identifier: None,
},
Value::BoolLiteral(b) => {
DeviceValue { symbol: Some(Symbol::BoolValue(*b)), identifier: None }
}
Value::Identifier(identifier) => match symbol_table.get(identifier) {
Some(symbol) => DeviceValue {
symbol: Some(symbol.clone()),
identifier: Some(identifier.clone()),
},
None => DeviceValue { symbol: None, identifier: Some(identifier.clone()) },
},
};
property_map.insert(key_symbol.clone(), device_value);
}
}
Ok(property_map)
}
fn evaluate_bind_program(&mut self) -> Result<bool, DebuggerError> {
let mut instructions = self.instructions.iter();
while let Some(mut instruction) = instructions.next() {
let mut jump_label = None;
match &instruction.instruction {
SymbolicInstruction::AbortIfEqual { lhs, rhs } => {
let aborts = self.device_property_matches(lhs, rhs);
self.output_abort_if(&instruction.location, aborts)?;
if aborts {
return Ok(false);
}
}
SymbolicInstruction::AbortIfNotEqual { lhs, rhs } => {
let aborts = !self.device_property_matches(lhs, rhs);
self.output_abort_if(&instruction.location, aborts)?;
if aborts {
return Ok(false);
}
}
SymbolicInstruction::Label(_label) => (),
SymbolicInstruction::UnconditionalJump { label } => {
jump_label = Some(label);
}
SymbolicInstruction::JumpIfEqual { lhs, rhs, label } => {
let jump_succeeds = self.device_property_matches(lhs, rhs);
self.output_jump_if_equal(&instruction.location, rhs, jump_succeeds)?;
if jump_succeeds {
jump_label = Some(label);
}
}
SymbolicInstruction::JumpIfNotEqual { lhs, rhs, label } => {
let jump_succeeds = !self.device_property_matches(lhs, rhs);
self.output_jump_if_not_equal(&instruction.location, jump_succeeds)?;
if jump_succeeds {
jump_label = Some(label);
}
}
SymbolicInstruction::UnconditionalAbort => {
self.output_unconditional_abort(&instruction.location)?;
return Ok(false);
}
SymbolicInstruction::UnconditionalBind => return Ok(true),
}
if let Some(label) = jump_label {
while instruction.instruction != SymbolicInstruction::Label(*label) {
instruction = instructions.next().ok_or(DebuggerError::MissingLabel)?;
}
}
}
Err(DebuggerError::NoOutcome)
}
fn device_property_matches(&self, lhs: &Symbol, rhs: &Symbol) -> bool {
if let Some(DeviceValue { symbol: Some(value_symbol), identifier: _ }) =
self.device_properties.get(lhs)
{
value_symbol == rhs
} else {
false
}
}
fn output_abort_if(
&mut self,
location: &'a Option<AstLocation>,
aborts: bool,
) -> Result<(), DebuggerError> {
if let Some(AstLocation::ConditionStatement(statement)) = location {
self.output.push(DebuggerOutput::ConditionStatement { statement, success: !aborts });
Ok(())
} else {
Err(DebuggerError::IncorrectAstLocation)
}
}
fn output_jump_if_equal(
&mut self,
location: &'a Option<AstLocation>,
rhs: &'a Symbol,
jump_succeeds: bool,
) -> Result<(), DebuggerError> {
match location {
Some(AstLocation::AcceptStatementValue { identifier, value, span }) => {
if jump_succeeds {
self.output.push(DebuggerOutput::AcceptStatementSuccess {
identifier,
value,
value_symbol: rhs,
span,
});
}
Ok(())
}
Some(AstLocation::IfCondition(condition)) => {
self.output
.push(DebuggerOutput::IfCondition { condition, success: !jump_succeeds });
Ok(())
}
_ => Err(DebuggerError::IncorrectAstLocation),
}
}
fn output_jump_if_not_equal(
&mut self,
location: &'a Option<AstLocation>,
jump_succeeds: bool,
) -> Result<(), DebuggerError> {
if let Some(AstLocation::IfCondition(condition)) = location {
self.output.push(DebuggerOutput::IfCondition { condition, success: !jump_succeeds });
Ok(())
} else {
Err(DebuggerError::IncorrectAstLocation)
}
}
fn output_unconditional_abort(
&mut self,
location: &'a Option<AstLocation>,
) -> Result<(), DebuggerError> {
match location {
Some(AstLocation::AbortStatement(statement)) => {
self.output.push(DebuggerOutput::AbortStatement { statement });
Ok(())
}
Some(AstLocation::AcceptStatementFailure { identifier, span, symbol: _ }) => {
self.output.push(DebuggerOutput::AcceptStatementFailure { identifier, span });
Ok(())
}
_ => Err(DebuggerError::IncorrectAstLocation),
}
}
fn log_output(&self) -> Result<(), DebuggerError> {
for output in &self.output {
match output {
DebuggerOutput::ConditionStatement { statement, success } => {
self.log_condition_statement(statement, *success)?;
}
DebuggerOutput::AbortStatement { statement } => self.log_abort_statement(statement),
DebuggerOutput::AcceptStatementSuccess {
identifier,
value,
value_symbol,
span,
} => self.log_accept_statement_success(identifier, value, value_symbol, span)?,
DebuggerOutput::AcceptStatementFailure { identifier, span } => {
self.log_accept_statement_failure(identifier, span)?;
}
DebuggerOutput::IfCondition { condition, success } => {
self.log_if_condition(condition, *success)?;
}
}
}
Ok(())
}
fn log_condition_statement(
&self,
statement: &Statement,
success: bool,
) -> Result<(), DebuggerError> {
if let Statement::ConditionStatement { span, condition: Condition { lhs, op, .. } } =
statement
{
let outcome_string = if success { "succeeded" } else { "failed" };
println!(
"Line {}: Condition statement {}: {}",
span.line, outcome_string, span.fragment
);
if condition_needs_actual_value(success, op) {
println!("\t{}", self.actual_value_string(lhs)?)
}
Ok(())
} else {
Err(DebuggerError::InvalidAstLocation)
}
}
fn log_abort_statement(&self, statement: &Statement) {
if let Statement::Abort { span } = statement {
println!("Line {}: Abort statement reached.", span.line);
}
}
fn log_accept_statement_success(
&self,
identifier: &CompoundIdentifier,
value: &Value,
value_symbol: &Symbol,
span: &Span,
) -> Result<(), DebuggerError> {
// Get the value identifier from the accept statement (or None for a literal value).
let value_identifier_prog =
if let Value::Identifier(identifier) = value { Some(identifier) } else { None };
// Get the value identifier from the device specification (or None for a literal value).
let key_symbol = self
.symbol_table
.get(identifier)
.ok_or(DebuggerError::UnknownKey(identifier.clone()))?;
let DeviceValue { symbol: _, identifier: value_identifier_device } = self
.device_properties
.get(key_symbol)
.expect("Accept statement succeeded so device must have this key.");
let value_literal = value_symbol_string(value_symbol)?;
let value_string = match (value_identifier_prog, value_identifier_device) {
(Some(identifier_prog), Some(identifier_value)) => {
if identifier_prog == identifier_value {
format!("`{}` [{}]", identifier_prog, value_literal)
} else {
format!("`{}` (`{}`) [{}]", identifier_prog, identifier_value, value_literal)
}
}
(Some(identifier_prog), None) => format!("`{}` [{}]", identifier_prog, value_literal),
(None, Some(identifier_value)) => format!("`{}` [{}]", identifier_value, value_literal),
(None, None) => format!("{}", value_literal),
};
println!(
"Line {}: Accept statement succeeded.\n\tValue of `{}` was {}.",
span.line, identifier, value_string
);
Ok(())
}
fn log_accept_statement_failure(
&self,
identifier: &CompoundIdentifier,
span: &Span,
) -> Result<(), DebuggerError> {
println!(
"Line {}: Accept statement failed.\n\t{}",
span.line,
self.actual_value_string(identifier)?
);
Ok(())
}
fn log_if_condition(&self, condition: &Condition, success: bool) -> Result<(), DebuggerError> {
let Condition { span, lhs, op, rhs: _ } = condition;
let outcome_string = if success { "succeeded" } else { "failed" };
println!(
"Line {}: If statement condition {}: {}",
span.line, outcome_string, span.fragment
);
if condition_needs_actual_value(success, op) {
println!("\t{}", self.actual_value_string(lhs)?)
}
Ok(())
}
fn actual_value_string(
&self,
key_identifier: &CompoundIdentifier,
) -> Result<String, DebuggerError> {
let key_symbol = self
.symbol_table
.get(key_identifier)
.ok_or(DebuggerError::UnknownKey(key_identifier.clone()))?;
let DeviceValue { symbol: value_symbol, identifier: value_identifier } = self
.device_properties
.get(key_symbol)
.unwrap_or(&DeviceValue { symbol: None, identifier: None });
Ok(match (value_symbol, value_identifier) {
(Some(symbol), Some(identifier)) => format!(
"Actual value of `{}` was `{}` [{}].",
key_identifier,
identifier,
value_symbol_string(symbol)?
),
(Some(symbol), None) => format!(
"Actual value of `{}` was literal {}.",
key_identifier,
value_symbol_string(symbol)?
),
(None, Some(identifier)) => {
format!("Actual value of `{}` was `{}`.", key_identifier, identifier)
}
(None, None) => format!("Device had no value for `{}`.", key_identifier),
})
}
}
fn condition_needs_actual_value(success: bool, op: &ConditionOp) -> bool {
match op {
ConditionOp::Equals => !success,
ConditionOp::NotEquals => success,
}
}
fn value_symbol_string(symbol: &Symbol) -> Result<String, DebuggerError> {
match symbol {
Symbol::DeprecatedKey(..) => Err(DebuggerError::InvalidValueSymbol(symbol.clone())),
Symbol::Key(..) => Err(DebuggerError::InvalidValueSymbol(symbol.clone())),
Symbol::NumberValue(n) => Ok(format!("0x{:x}", n)),
Symbol::StringValue(s) => Ok(format!("\"{}\"", s)),
Symbol::BoolValue(b) => Ok(b.to_string()),
Symbol::EnumValue => unimplemented!("Enum values are unsupported."),
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::bind_library;
use crate::bind_program::{Condition, ConditionOp, Statement};
use crate::make_identifier;
use crate::parser_common::{CompoundIdentifier, Span};
#[test]
fn duplicate_key() {
let symbol_table = HashMap::new();
let properties = vec![
Property { key: make_identifier!("abc"), value: Value::NumericLiteral(42) },
Property { key: make_identifier!("abc"), value: Value::NumericLiteral(5) },
];
assert_eq!(
Debugger::construct_property_map(&properties, &symbol_table),
Err(DebuggerError::DuplicateKey(make_identifier!("abc")))
);
}
#[test]
fn condition_equals() {
let statement = Statement::ConditionStatement {
span: Span::new(),
condition: Condition {
span: Span::new(),
lhs: make_identifier!("abc"),
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(42),
},
};
let statements = vec![statement.clone()];
let mut symbol_table = HashMap::new();
symbol_table.insert(
make_identifier!("abc"),
Symbol::Key("abc".to_string(), bind_library::ValueType::Number),
);
let instructions = compiler::compile_statements(statements, &symbol_table).unwrap();
// Binds when the device has the correct property.
let properties =
vec![Property { key: make_identifier!("abc"), value: Value::NumericLiteral(42) }];
let mut debugger = Debugger::new(&properties, &instructions, &symbol_table).unwrap();
assert!(debugger.evaluate_bind_program().unwrap());
assert_eq!(
debugger.output,
vec![DebuggerOutput::ConditionStatement { statement: &statement, success: true }]
);
// Binds when other properties are present as well.
let properties = vec![
Property { key: make_identifier!("abc"), value: Value::NumericLiteral(42) },
Property { key: make_identifier!("xyz"), value: Value::BoolLiteral(true) },
];
let mut debugger = Debugger::new(&properties, &instructions, &symbol_table).unwrap();
assert!(debugger.evaluate_bind_program().unwrap());
assert_eq!(
debugger.output,
vec![DebuggerOutput::ConditionStatement { statement: &statement, success: true }]
);
// Doesn't bind when the device has the wrong value for the property.
let properties =
vec![Property { key: make_identifier!("abc"), value: Value::NumericLiteral(5) }];
let mut debugger = Debugger::new(&properties, &instructions, &symbol_table).unwrap();
assert!(!debugger.evaluate_bind_program().unwrap());
assert_eq!(
debugger.output,
vec![DebuggerOutput::ConditionStatement { statement: &statement, success: false }]
);
// Doesn't bind when the property is not present in the device.
let properties = Vec::new();
let mut debugger = Debugger::new(&properties, &instructions, &symbol_table).unwrap();
assert!(!debugger.evaluate_bind_program().unwrap());
assert_eq!(
debugger.output,
vec![DebuggerOutput::ConditionStatement { statement: &statement, success: false }]
);
}
#[test]
fn condition_not_equals() {
let statement = Statement::ConditionStatement {
span: Span::new(),
condition: Condition {
span: Span::new(),
lhs: make_identifier!("abc"),
op: ConditionOp::NotEquals,
rhs: Value::NumericLiteral(42),
},
};
let statements = vec![statement.clone()];
let mut symbol_table = HashMap::new();
symbol_table.insert(
make_identifier!("abc"),
Symbol::Key("abc".to_string(), bind_library::ValueType::Number),
);
let instructions = compiler::compile_statements(statements, &symbol_table).unwrap();
// Binds when the device has a different value for the property.
let properties =
vec![Property { key: make_identifier!("abc"), value: Value::NumericLiteral(5) }];
let mut debugger = Debugger::new(&properties, &instructions, &symbol_table).unwrap();
assert!(debugger.evaluate_bind_program().unwrap());
assert_eq!(
debugger.output,
vec![DebuggerOutput::ConditionStatement { statement: &statement, success: true }]
);
// Binds when the property is not present in the device.
let properties = Vec::new();
let mut debugger = Debugger::new(&properties, &instructions, &symbol_table).unwrap();
assert!(debugger.evaluate_bind_program().unwrap());
assert_eq!(
debugger.output,
vec![DebuggerOutput::ConditionStatement { statement: &statement, success: true }]
);
// Doesn't bind when the device has the property in the condition statement.
let properties =
vec![Property { key: make_identifier!("abc"), value: Value::NumericLiteral(42) }];
let mut debugger = Debugger::new(&properties, &instructions, &symbol_table).unwrap();
assert!(!debugger.evaluate_bind_program().unwrap());
assert_eq!(
debugger.output,
vec![DebuggerOutput::ConditionStatement { statement: &statement, success: false }]
);
}
#[test]
fn accept() {
let statements = vec![Statement::Accept {
span: Span::new(),
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::Key("abc".to_string(), bind_library::ValueType::Number),
);
let instructions = compiler::compile_statements(statements, &symbol_table).unwrap();
// Binds when the device has one of the accepted values for the property.
let properties =
vec![Property { key: make_identifier!("abc"), value: Value::NumericLiteral(42) }];
let mut debugger = Debugger::new(&properties, &instructions, &symbol_table).unwrap();
assert!(debugger.evaluate_bind_program().unwrap());
assert_eq!(
debugger.output,
vec![DebuggerOutput::AcceptStatementSuccess {
identifier: &make_identifier!("abc"),
value: &Value::NumericLiteral(42),
value_symbol: &Symbol::NumberValue(42),
span: &Span::new(),
}]
);
// Doesn't bind when the device has a different value for the property.
let properties =
vec![Property { key: make_identifier!("abc"), value: Value::NumericLiteral(5) }];
let mut debugger = Debugger::new(&properties, &instructions, &symbol_table).unwrap();
assert!(!debugger.evaluate_bind_program().unwrap());
assert_eq!(
debugger.output,
vec![DebuggerOutput::AcceptStatementFailure {
identifier: &make_identifier!("abc"),
span: &Span::new(),
}]
);
// Doesn't bind when the device is missing the property.
let properties = Vec::new();
let mut debugger = Debugger::new(&properties, &instructions, &symbol_table).unwrap();
assert!(!debugger.evaluate_bind_program().unwrap());
assert_eq!(
debugger.output,
vec![DebuggerOutput::AcceptStatementFailure {
identifier: &make_identifier!("abc"),
span: &Span::new(),
}]
);
}
#[test]
fn if_else() {
/*
if abc == 1 {
xyz == 1;
} else if abc == 2{
xyz == 2;
} else {
xyz == 3;
}
*/
let condition1 = Condition {
span: Span::new(),
lhs: make_identifier!("abc"),
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(1),
};
let condition2 = Condition {
span: Span::new(),
lhs: make_identifier!("abc"),
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(2),
};
let statement1 = Statement::ConditionStatement {
span: Span::new(),
condition: Condition {
span: Span::new(),
lhs: make_identifier!("xyz"),
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(1),
},
};
let statement2 = Statement::ConditionStatement {
span: Span::new(),
condition: Condition {
span: Span::new(),
lhs: make_identifier!("xyz"),
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(2),
},
};
let statement3 = Statement::ConditionStatement {
span: Span::new(),
condition: Condition {
span: Span::new(),
lhs: make_identifier!("xyz"),
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(3),
},
};
let statements = vec![Statement::If {
span: Span::new(),
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::Key("abc".to_string(), bind_library::ValueType::Number),
);
symbol_table.insert(
make_identifier!("xyz"),
Symbol::Key("xyz".to_string(), bind_library::ValueType::Number),
);
let instructions = compiler::compile_statements(statements, &symbol_table).unwrap();
// Binds when the if clause is satisfied.
let properties = vec![
Property { key: make_identifier!("abc"), value: Value::NumericLiteral(1) },
Property { key: make_identifier!("xyz"), value: Value::NumericLiteral(1) },
];
let mut debugger = Debugger::new(&properties, &instructions, &symbol_table).unwrap();
assert!(debugger.evaluate_bind_program().unwrap());
assert_eq!(
debugger.output,
vec![
DebuggerOutput::IfCondition { condition: &condition1, success: true },
DebuggerOutput::ConditionStatement { statement: &statement1, success: true }
]
);
// Binds when the if else clause is satisfied.
let properties = vec![
Property { key: make_identifier!("abc"), value: Value::NumericLiteral(2) },
Property { key: make_identifier!("xyz"), value: Value::NumericLiteral(2) },
];
let mut debugger = Debugger::new(&properties, &instructions, &symbol_table).unwrap();
assert!(debugger.evaluate_bind_program().unwrap());
assert_eq!(
debugger.output,
vec![
DebuggerOutput::IfCondition { condition: &condition1, success: false },
DebuggerOutput::IfCondition { condition: &condition2, success: true },
DebuggerOutput::ConditionStatement { statement: &statement2, success: true }
]
);
// Binds when the else clause is satisfied.
let properties =
vec![Property { key: make_identifier!("xyz"), value: Value::NumericLiteral(3) }];
let mut debugger = Debugger::new(&properties, &instructions, &symbol_table).unwrap();
assert!(debugger.evaluate_bind_program().unwrap());
assert_eq!(
debugger.output,
vec![
DebuggerOutput::IfCondition { condition: &condition1, success: false },
DebuggerOutput::IfCondition { condition: &condition2, success: false },
DebuggerOutput::ConditionStatement { statement: &statement3, success: true }
]
);
// Doesn't bind when the device has incorrect values for the properties.
let properties = vec![
Property { key: make_identifier!("abc"), value: Value::NumericLiteral(42) },
Property { key: make_identifier!("xyz"), value: Value::NumericLiteral(42) },
];
let mut debugger = Debugger::new(&properties, &instructions, &symbol_table).unwrap();
assert!(!debugger.evaluate_bind_program().unwrap());
assert_eq!(
debugger.output,
vec![
DebuggerOutput::IfCondition { condition: &condition1, success: false },
DebuggerOutput::IfCondition { condition: &condition2, success: false },
DebuggerOutput::ConditionStatement { statement: &statement3, success: false }
]
);
// Doesn't bind when the properties are missing in the device.
let properties = Vec::new();
let mut debugger = Debugger::new(&properties, &instructions, &symbol_table).unwrap();
assert!(!debugger.evaluate_bind_program().unwrap());
assert_eq!(
debugger.output,
vec![
DebuggerOutput::IfCondition { condition: &condition1, success: false },
DebuggerOutput::IfCondition { condition: &condition2, success: false },
DebuggerOutput::ConditionStatement { statement: &statement3, success: false }
]
);
}
#[test]
fn abort() {
let condition_statement = Statement::ConditionStatement {
span: Span::new(),
condition: Condition {
span: Span::new(),
lhs: make_identifier!("abc"),
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(42),
},
};
let abort_statement = Statement::Abort { span: Span::new() };
let statements = vec![condition_statement.clone(), abort_statement.clone()];
let mut symbol_table = HashMap::new();
symbol_table.insert(
make_identifier!("abc"),
Symbol::Key("abc".to_string(), bind_library::ValueType::Number),
);
let instructions = compiler::compile_statements(statements, &symbol_table).unwrap();
// Doesn't bind when abort statement is present.
let properties =
vec![Property { key: make_identifier!("abc"), value: Value::NumericLiteral(42) }];
let mut debugger = Debugger::new(&properties, &instructions, &symbol_table).unwrap();
assert!(!debugger.evaluate_bind_program().unwrap());
assert_eq!(
debugger.output,
vec![
DebuggerOutput::ConditionStatement {
statement: &condition_statement,
success: true
},
DebuggerOutput::AbortStatement { statement: &abort_statement }
]
);
}
#[test]
fn supports_all_value_types() {
let statements = vec![
Statement::ConditionStatement {
span: Span::new(),
condition: Condition {
span: Span::new(),
lhs: make_identifier!("a"),
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(42),
},
},
Statement::ConditionStatement {
span: Span::new(),
condition: Condition {
span: Span::new(),
lhs: make_identifier!("b"),
op: ConditionOp::Equals,
rhs: Value::BoolLiteral(false),
},
},
Statement::ConditionStatement {
span: Span::new(),
condition: Condition {
span: Span::new(),
lhs: make_identifier!("c"),
op: ConditionOp::Equals,
rhs: Value::StringLiteral("string".to_string()),
},
},
Statement::ConditionStatement {
span: Span::new(),
condition: Condition {
span: Span::new(),
lhs: make_identifier!("d"),
op: ConditionOp::Equals,
rhs: Value::Identifier(make_identifier!("VALUE")),
},
},
];
let mut symbol_table = HashMap::new();
symbol_table.insert(
make_identifier!("a"),
Symbol::Key("a".to_string(), bind_library::ValueType::Number),
);
symbol_table.insert(
make_identifier!("b"),
Symbol::Key("b".to_string(), bind_library::ValueType::Number),
);
symbol_table.insert(
make_identifier!("c"),
Symbol::Key("c".to_string(), bind_library::ValueType::Number),
);
symbol_table.insert(
make_identifier!("d"),
Symbol::Key("d".to_string(), bind_library::ValueType::Number),
);
symbol_table.insert(
make_identifier!("VALUE"),
Symbol::Key("VALUE".to_string(), bind_library::ValueType::Number),
);
let instructions = compiler::compile_statements(statements, &symbol_table).unwrap();
// Binds when other properties are present as well.
let properties = vec![
Property { key: make_identifier!("a"), value: Value::NumericLiteral(42) },
Property { key: make_identifier!("b"), value: Value::BoolLiteral(false) },
Property {
key: make_identifier!("c"),
value: Value::StringLiteral("string".to_string()),
},
Property {
key: make_identifier!("d"),
value: Value::Identifier(make_identifier!("VALUE")),
},
];
let mut debugger = Debugger::new(&properties, &instructions, &symbol_table).unwrap();
assert!(debugger.evaluate_bind_program().unwrap());
}
#[test]
fn full_program() {
/*
if abc == 42 {
abort;
} else {
accept xyz {1, 2};
pqr != true;
}
*/
let condition = Condition {
span: Span::new(),
lhs: make_identifier!("abc"),
op: ConditionOp::Equals,
rhs: Value::NumericLiteral(42),
};
let abort_statement = Statement::Abort { span: Span::new() };
let condition_statement = Statement::ConditionStatement {
span: Span::new(),
condition: Condition {
span: Span::new(),
lhs: make_identifier!("pqr"),
op: ConditionOp::NotEquals,
rhs: Value::BoolLiteral(true),
},
};
let statements = vec![Statement::If {
span: Span::new(),
blocks: vec![(condition.clone(), vec![abort_statement.clone()])],
else_block: vec![
Statement::Accept {
span: Span::new(),
identifier: make_identifier!("xyz"),
values: vec![Value::NumericLiteral(1), Value::NumericLiteral(2)],
},
condition_statement.clone(),
],
}];
let mut symbol_table = HashMap::new();
symbol_table.insert(
make_identifier!("abc"),
Symbol::Key("abc".to_string(), bind_library::ValueType::Number),
);
symbol_table.insert(
make_identifier!("xyz"),
Symbol::Key("xyz".to_string(), bind_library::ValueType::Number),
);
symbol_table.insert(
make_identifier!("pqr"),
Symbol::Key("pqr".to_string(), bind_library::ValueType::Number),
);
let instructions = compiler::compile_statements(statements, &symbol_table).unwrap();
// Aborts because if condition is true.
let properties =
vec![Property { key: make_identifier!("abc"), value: Value::NumericLiteral(42) }];
let mut debugger = Debugger::new(&properties, &instructions, &symbol_table).unwrap();
assert!(!debugger.evaluate_bind_program().unwrap());
assert_eq!(
debugger.output,
vec![
DebuggerOutput::IfCondition { condition: &condition, success: true },
DebuggerOutput::AbortStatement { statement: &abort_statement }
]
);
// Binds because all statements inside else block are satisfied.
let properties = vec![
Property { key: make_identifier!("abc"), value: Value::NumericLiteral(43) },
Property { key: make_identifier!("xyz"), value: Value::NumericLiteral(1) },
];
let mut debugger = Debugger::new(&properties, &instructions, &symbol_table).unwrap();
assert!(debugger.evaluate_bind_program().unwrap());
assert_eq!(
debugger.output,
vec![
DebuggerOutput::IfCondition { condition: &condition, success: false },
DebuggerOutput::AcceptStatementSuccess {
identifier: &make_identifier!("xyz"),
value: &Value::NumericLiteral(1),
value_symbol: &Symbol::NumberValue(1),
span: &Span::new(),
},
DebuggerOutput::ConditionStatement {
statement: &condition_statement,
success: true
}
]
);
// Doesn't bind because accept statement is not satisfied.
let properties = vec![
Property { key: make_identifier!("abc"), value: Value::NumericLiteral(43) },
Property { key: make_identifier!("xyz"), value: Value::NumericLiteral(3) },
];
let mut debugger = Debugger::new(&properties, &instructions, &symbol_table).unwrap();
assert!(!debugger.evaluate_bind_program().unwrap());
assert_eq!(
debugger.output,
vec![
DebuggerOutput::IfCondition { condition: &condition, success: false },
DebuggerOutput::AcceptStatementFailure {
identifier: &make_identifier!("xyz"),
span: &Span::new(),
},
]
);
// Doesn't bind because condition statement is not satisfied.
let properties = vec![
Property { key: make_identifier!("abc"), value: Value::NumericLiteral(43) },
Property { key: make_identifier!("xyz"), value: Value::NumericLiteral(1) },
Property { key: make_identifier!("pqr"), value: Value::BoolLiteral(true) },
];
let mut debugger = Debugger::new(&properties, &instructions, &symbol_table).unwrap();
assert!(!debugger.evaluate_bind_program().unwrap());
assert_eq!(
debugger.output,
vec![
DebuggerOutput::IfCondition { condition: &condition, success: false },
DebuggerOutput::AcceptStatementSuccess {
identifier: &make_identifier!("xyz"),
value: &Value::NumericLiteral(1),
value_symbol: &Symbol::NumberValue(1),
span: &Span::new(),
},
DebuggerOutput::ConditionStatement {
statement: &condition_statement,
success: false
}
]
);
}
}