use crate::{
A2lError, ItemList,
module::{AnyCompuTab, AnyObject, AnyTypedef},
specification::*,
};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq)]
enum CharacteristicWrapper<'a> {
Characteristic(&'a Characteristic),
TypedefCharacteristic(&'a TypedefCharacteristic),
}
#[must_use]
pub fn check(a2l_file: &A2lFile) -> Vec<A2lError> {
let mut results = Vec::new();
for module in &a2l_file.project.module {
let compu_tabs = module.compu_tabs();
let objects = module.objects();
let typedefs = module.typedefs();
for axis_pts in &module.axis_pts {
check_axis_pts(axis_pts, module, &objects, &mut results);
}
for t_axis in &module.typedef_axis {
check_typedef_axis(t_axis, module, &objects, &mut results);
}
for characteristic in &module.characteristic {
check_characteristic(characteristic, module, &objects, &mut results);
}
for t_characteristic in &module.typedef_characteristic {
let is_directly_used = module
.instance
.iter()
.any(|instance| instance.type_ref == t_characteristic.name);
let containing_structures: Vec<&TypedefStructure> = module
.typedef_structure
.iter()
.filter(|ts| {
ts.structure_component
.iter()
.any(|sc| sc.component_type == t_characteristic.name)
})
.collect();
check_typedef_characteristic(
t_characteristic,
module,
&objects,
is_directly_used,
&containing_structures,
&mut results,
);
}
for compu_method in &module.compu_method {
check_compu_method(compu_method, module, &compu_tabs, &mut results);
}
for function in &module.function {
check_function(function, module, &objects, &mut results);
}
for group in &module.group {
check_group(group, module, &objects, &mut results);
}
check_group_structure(&module.group, &mut results);
for measurement in &module.measurement {
check_measurement(measurement, module, &mut results);
}
for t_measurement in &module.typedef_measurement {
check_typedef_measurement(t_measurement, module, &mut results);
}
for transformer in &module.transformer {
check_transformer(transformer, module, &objects, &mut results);
}
for instance in &module.instance {
check_instance(instance, &typedefs, &mut results);
}
for typedef_structure in &module.typedef_structure {
check_typedef_structure(typedef_structure, &typedefs, &mut results);
}
}
results
}
fn check_axis_descr(
idx: usize,
parent_name: &str,
axis_descr: &AxisDescr,
module: &Module,
objects: &ItemList<AnyObject>,
log_msgs: &mut Vec<A2lError>,
) {
let line = axis_descr.get_line();
if axis_descr.input_quantity != "NO_INPUT_QUANTITY"
&& !objects.contains_key(&axis_descr.input_quantity)
&& module
.find_instance_component(&axis_descr.input_quantity)
.is_none()
{
log_msgs.push(A2lError::CrossReferenceError {
source_type: format!("AXIS_DESCR[{idx}] of CHARACTERISTIC"),
source_name: parent_name.to_string(),
source_line: line,
target_type: "MEASUREMENT".to_string(),
target_name: axis_descr.input_quantity.to_string(),
});
}
if axis_descr.conversion != "NO_COMPU_METHOD"
&& !module.compu_method.contains_key(&axis_descr.conversion)
{
log_msgs.push(A2lError::CrossReferenceError {
source_type: format!("AXIS_DESCR[{idx}] of CHARACTERISTIC"),
source_name: parent_name.to_string(),
source_line: line,
target_type: "COMPU_METHOD".to_string(),
target_name: axis_descr.conversion.to_string(),
});
}
match (axis_descr.attribute, axis_descr.axis_pts_ref.is_some()) {
(AxisDescrAttribute::ComAxis, false) | (AxisDescrAttribute::ResAxis, false) => {
log_msgs.push(A2lError::ContentError {
item_name: parent_name.to_string(),
blockname: format!("AXIS_DESCR[{idx}] of CHARACTERISTIC"),
line,
description: format!("{} requires an AXIS_PTS_REF.", axis_descr.attribute),
});
}
(AxisDescrAttribute::StdAxis, true) | (AxisDescrAttribute::FixAxis, true) => {
log_msgs.push(A2lError::ContentError {
item_name: parent_name.to_string(),
blockname: format!("AXIS_DESCR[{idx}] of CHARACTERISTIC"),
line,
description: format!("{} does not use AXIS_PTS_REF.", axis_descr.attribute),
});
}
_ => {}
}
if axis_descr.attribute != AxisDescrAttribute::CurveAxis && axis_descr.curve_axis_ref.is_some()
{
log_msgs.push(A2lError::ContentError {
item_name: parent_name.to_string(),
blockname: format!("AXIS_DESCR[{idx}] of CHARACTERISTIC"),
line,
description: "Only AXIS_DESCR of type CURVE_AXIS can have a CURVE_AXIS_REF."
.to_string(),
});
} else if axis_descr.attribute == AxisDescrAttribute::CurveAxis
&& axis_descr.curve_axis_ref.is_none()
{
log_msgs.push(A2lError::ContentError {
item_name: parent_name.to_string(),
blockname: format!("AXIS_DESCR[{idx}] of CHARACTERISTIC"),
line,
description: "AXIS_DESCR of type CURVE_AXIS must have a CURVE_AXIS_REF.".to_string(),
});
}
}
#[allow(clippy::too_many_arguments)]
fn check_axis_descr_refs(
module: &Module,
idx: usize,
parent_name: &str,
axis_descr: &AxisDescr,
objects: &ItemList<AnyObject>,
is_directly_used: bool,
containing_structures: &[&TypedefStructure],
log_msgs: &mut Vec<A2lError>,
) {
if let Some(axis_pts_ref) = &axis_descr.axis_pts_ref {
let apr_line = axis_pts_ref.get_line();
if !is_directly_used
&& !containing_structures.is_empty()
&& axis_pts_ref.axis_points.starts_with("THIS.")
{
let structure_component = axis_pts_ref.axis_points.strip_prefix("THIS.").unwrap();
if !is_valid_structure_component(structure_component, containing_structures) {
log_msgs.push(A2lError::CrossReferenceError {
source_type: format!("AXIS_DESCR[{idx}] of CHARACTERISTIC"),
source_name: parent_name.to_string(),
source_line: apr_line,
target_type: "STRUCTURE_COMPONENT".to_string(),
target_name: structure_component.to_string(),
});
}
} else if !objects.contains_key(&axis_pts_ref.axis_points)
&& module
.find_instance_component(&axis_pts_ref.axis_points)
.is_none()
{
log_msgs.push(A2lError::CrossReferenceError {
source_type: format!("AXIS_DESCR[{idx}] of CHARACTERISTIC"),
source_name: parent_name.to_string(),
source_line: apr_line,
target_type: "AXIS_PTS".to_string(),
target_name: axis_pts_ref.axis_points.to_string(),
});
}
}
if let Some(curve_axis_ref) = &axis_descr.curve_axis_ref {
let car_line = curve_axis_ref.get_line();
if !is_directly_used
&& !containing_structures.is_empty()
&& curve_axis_ref.curve_axis.starts_with("THIS.")
{
let structure_component = curve_axis_ref.curve_axis.strip_prefix("THIS.").unwrap();
if !is_valid_structure_component(structure_component, containing_structures) {
log_msgs.push(A2lError::CrossReferenceError {
source_type: format!("AXIS_DESCR[{idx}] of CHARACTERISTIC"),
source_name: parent_name.to_string(),
source_line: car_line,
target_type: "STRUCTURE_COMPONENT".to_string(),
target_name: structure_component.to_string(),
});
}
} else if !objects.contains_key(&curve_axis_ref.curve_axis)
&& module
.find_instance_component(&curve_axis_ref.curve_axis)
.is_none()
{
log_msgs.push(A2lError::CrossReferenceError {
source_type: format!("AXIS_DESCR[{idx}] of CHARACTERISTIC"),
source_name: parent_name.to_string(),
source_line: car_line,
target_type: "CHARACTERISTIC".to_string(),
target_name: curve_axis_ref.curve_axis.to_string(),
});
}
}
}
fn is_valid_structure_component(
structure_component: &str,
containing_structures: &[&TypedefStructure],
) -> bool {
containing_structures.iter().all(|td_struct| {
td_struct
.structure_component
.iter()
.any(|sc| sc.name == structure_component)
})
}
fn check_typedef_axis(
t_axis: &TypedefAxis,
module: &Module,
objects: &ItemList<AnyObject>,
log_msgs: &mut Vec<A2lError>,
) {
let name = t_axis.get_name();
let line = t_axis.get_line();
if t_axis.input_quantity != "NO_INPUT_QUANTITY"
&& !objects.contains_key(&t_axis.input_quantity)
&& module
.find_instance_component(&t_axis.input_quantity)
.is_none()
{
log_msgs.push(A2lError::CrossReferenceError {
source_type: "TYPEDEF_AXIS".to_string(),
source_name: name.to_string(),
source_line: line,
target_type: "MEASUREMENT".to_string(),
target_name: t_axis.input_quantity.to_string(),
});
}
if !module.record_layout.contains_key(&t_axis.record_layout) {
log_msgs.push(A2lError::CrossReferenceError {
source_type: "TYPEDEF_AXIS".to_string(),
source_name: name.to_string(),
source_line: line,
target_type: "RECORD_LAYOUT".to_string(),
target_name: t_axis.record_layout.to_string(),
});
}
if t_axis.conversion != "NO_COMPU_METHOD"
&& !module.compu_method.contains_key(&t_axis.conversion)
{
log_msgs.push(A2lError::CrossReferenceError {
source_type: "TYPEDEF_AXIS".to_string(),
source_name: name.to_string(),
source_line: line,
target_type: "COMPU_METHOD".to_string(),
target_name: t_axis.conversion.to_string(),
});
}
}
fn check_axis_pts(
axis_pts: &AxisPts,
module: &Module,
objects: &ItemList<AnyObject>,
log_msgs: &mut Vec<A2lError>,
) {
let name = &axis_pts.name;
let line = axis_pts.get_line();
if axis_pts.conversion != "NO_COMPU_METHOD"
&& !module.compu_method.contains_key(&axis_pts.conversion)
{
log_msgs.push(A2lError::CrossReferenceError {
source_type: "AXIS_PTS".to_string(),
source_name: name.to_string(),
source_line: line,
target_type: "COMPU_METHOD".to_string(),
target_name: axis_pts.conversion.to_string(),
});
}
if axis_pts.input_quantity != "NO_INPUT_QUANTITY"
&& !objects.contains_key(&axis_pts.input_quantity)
&& module
.find_instance_component(&axis_pts.input_quantity)
.is_none()
{
log_msgs.push(A2lError::CrossReferenceError {
source_type: "AXIS_PTS".to_string(),
source_name: name.to_string(),
source_line: line,
target_type: "MEASUREMENT".to_string(),
target_name: axis_pts.input_quantity.to_string(),
});
}
if let Some(rl) = module.record_layout.get(&axis_pts.deposit_record) {
if let Some(axis_pts_x) = &rl.axis_pts_x {
let opt_compu_method = module.compu_method.get(&axis_pts.conversion);
let calculated_limits = calc_compu_method_limits(opt_compu_method, axis_pts_x.datatype);
let existing_limits = (axis_pts.lower_limit, axis_pts.upper_limit);
if !check_limits_valid(existing_limits, calculated_limits) {
log_msgs.push(A2lError::LimitCheckError {
item_name: name.to_string(),
blockname: "AXIS_PTS".to_string(),
line,
lower_limit: axis_pts.lower_limit,
upper_limit: axis_pts.upper_limit,
calculated_lower_limit: calculated_limits.0,
calculated_upper_limit: calculated_limits.1,
});
}
} else {
log_msgs.push(A2lError::ContentError {
item_name: name.to_string(),
blockname: "AXIS_PTS".to_string(),
line,
description: format!(
"Referenced RECORD_LAYOUT {} does not have AXIS_PTS_X.",
axis_pts.deposit_record
),
});
}
} else {
log_msgs.push(A2lError::CrossReferenceError {
source_type: "AXIS_PTS".to_string(),
source_name: name.to_string(),
source_line: line,
target_type: "RECORD_LAYOUT".to_string(),
target_name: axis_pts.deposit_record.to_string(),
});
}
check_function_list(&axis_pts.function_list, module, log_msgs);
check_ref_memory_segment(&axis_pts.ref_memory_segment, module, log_msgs);
}
fn check_characteristic(
characteristic: &Characteristic,
module: &Module,
objects: &ItemList<AnyObject>,
results: &mut Vec<A2lError>,
) {
check_characteristic_common(
CharacteristicWrapper::Characteristic(characteristic),
module,
objects,
true, &[],
results,
);
if let Some(comparison_quantity) = &characteristic.comparison_quantity {
let cqline = comparison_quantity.get_line();
if !objects.contains_key(&comparison_quantity.name)
&& module
.find_instance_component(&comparison_quantity.name)
.is_none()
{
results.push(A2lError::CrossReferenceError {
source_type: "CHARACTERISTIC".to_string(),
source_name: characteristic.name.to_string(),
source_line: cqline,
target_type: "MEASUREMENT".to_string(),
target_name: comparison_quantity.name.to_string(),
});
}
}
if let Some(dependent_characteristic) = &characteristic.dependent_characteristic {
let depline = dependent_characteristic.get_line();
check_reference_list(
"DEPENDENT_CHARACTERISTIC",
"CHARACTERISTIC",
depline,
&dependent_characteristic.characteristic_list,
Some(module),
objects,
results,
);
}
if let Some(map_list) = &characteristic.map_list {
let ml_line = map_list.get_line();
check_reference_list(
"MAP_LIST",
"CHARACTERISTIC",
ml_line,
&map_list.name_list,
Some(module),
objects,
results,
);
}
if let Some(virtual_characteristic) = &characteristic.virtual_characteristic {
let vc_line = virtual_characteristic.get_line();
check_reference_list(
"VIRTUAL_CHARACTERISTIC",
"CHARACTERISTIC",
vc_line,
&virtual_characteristic.characteristic_list,
Some(module),
objects,
results,
);
}
check_function_list(&characteristic.function_list, module, results);
check_ref_memory_segment(&characteristic.ref_memory_segment, module, results);
}
fn check_typedef_characteristic(
t_characteristic: &TypedefCharacteristic,
module: &Module,
objects: &ItemList<AnyObject>,
is_directly_used: bool,
containing_structures: &[&TypedefStructure],
log_msgs: &mut Vec<A2lError>,
) {
check_characteristic_common(
CharacteristicWrapper::TypedefCharacteristic(t_characteristic),
module,
objects,
is_directly_used,
containing_structures,
log_msgs,
);
}
fn check_characteristic_common(
characteristic: CharacteristicWrapper,
module: &Module,
objects: &ItemList<AnyObject>,
is_directly_used: bool,
containing_structures: &[&TypedefStructure],
log_msgs: &mut Vec<A2lError>,
) {
let kind = characteristic.kind();
let name = characteristic.name();
let line = characteristic.line();
if characteristic.conversion() != "NO_COMPU_METHOD"
&& !module
.compu_method
.contains_key(characteristic.conversion())
{
log_msgs.push(A2lError::CrossReferenceError {
source_type: kind.to_string(),
source_name: name.to_string(),
source_line: line,
target_type: "COMPU_METHOD".to_string(),
target_name: characteristic.conversion().to_string(),
});
}
for (idx, axis_descr) in characteristic.axis_descr().iter().enumerate() {
check_axis_descr(idx, name, axis_descr, module, objects, log_msgs);
check_axis_descr_refs(
module,
idx,
name,
axis_descr,
objects,
is_directly_used,
containing_structures,
log_msgs,
);
}
let mut expected_axis_count = match characteristic.characteristic_type() {
CharacteristicType::Value | CharacteristicType::ValBlk | CharacteristicType::Ascii => 0,
CharacteristicType::Curve => 1,
CharacteristicType::Map => 2,
CharacteristicType::Cuboid => 3,
CharacteristicType::Cube4 => 4,
CharacteristicType::Cube5 => 5,
};
if characteristic.characteristic_type() == CharacteristicType::Cuboid
&& characteristic.map_list().is_some()
{
expected_axis_count = 1; }
if characteristic.axis_descr().len() != expected_axis_count {
log_msgs.push(A2lError::ContentError {
item_name: name.to_string(),
blockname: kind.to_string(),
line,
description: format!(
"Expected {expected_axis_count} AXIS_DESCR for type {}, found {}",
characteristic.characteristic_type(),
characteristic.axis_descr().len()
),
});
}
let rl_name = characteristic.record_layout();
if let Some(record_layout) = module.record_layout.get(rl_name) {
if let Some(fnc_values) = &record_layout.fnc_values {
let opt_compu_method = module.compu_method.get(characteristic.conversion());
let calculated_limits = calc_compu_method_limits(opt_compu_method, fnc_values.datatype);
let existing_limits = (characteristic.lower_limit(), characteristic.upper_limit());
if !check_limits_valid(existing_limits, calculated_limits) {
log_msgs.push(A2lError::LimitCheckError {
item_name: name.to_string(),
blockname: kind.to_string(),
line,
lower_limit: characteristic.lower_limit(),
upper_limit: characteristic.upper_limit(),
calculated_lower_limit: calculated_limits.0,
calculated_upper_limit: calculated_limits.1,
});
}
} else {
log_msgs.push(A2lError::ContentError {
item_name: name.to_string(),
blockname: kind.to_string(),
line,
description: format!(
"Referenced RECORD_LAYOUT {rl_name} does not have FNC_VALUES."
),
});
}
let axis_refs = [
&record_layout.axis_pts_x,
&record_layout.axis_pts_y,
&record_layout.axis_pts_z,
&record_layout.axis_pts_4,
&record_layout.axis_pts_5,
];
let axis_pts_names = ["X", "Y", "Z", "4", "5"];
for (idx, axis_descr) in characteristic.axis_descr().iter().enumerate() {
if axis_descr.attribute == AxisDescrAttribute::StdAxis {
if let Some(axis_pts_dim) = axis_refs[idx] {
let opt_compu_method = module.compu_method.get(&axis_descr.conversion);
let calculated_limits =
calc_compu_method_limits(opt_compu_method, axis_pts_dim.datatype);
let existing_limits = (axis_descr.lower_limit, axis_descr.upper_limit);
if !check_limits_valid(existing_limits, calculated_limits) {
let ad_line = axis_descr.get_line();
log_msgs.push(A2lError::LimitCheckError {
item_name: name.to_string(),
blockname: "AXIS_DESCR".to_string(),
line: ad_line,
lower_limit: axis_descr.lower_limit,
upper_limit: axis_descr.upper_limit,
calculated_lower_limit: calculated_limits.0,
calculated_upper_limit: calculated_limits.1,
});
}
} else {
log_msgs.push(A2lError::ContentError {
item_name: name.to_string(),
blockname: kind.to_string(),
line,
description: format!(
"Referenced RECORD_LAYOUT {rl_name} does not have AXIS_PTS_{}.",
axis_pts_names[idx]
),
});
}
}
}
} else {
log_msgs.push(A2lError::CrossReferenceError {
source_type: kind.to_string(),
source_name: name.to_string(),
source_line: line,
target_type: "RECORD_LAYOUT".to_string(),
target_name: rl_name.to_string(),
});
}
}
fn check_compu_method(
compu_method: &CompuMethod,
module: &Module,
compu_tabs: &ItemList<AnyCompuTab>,
log_msgs: &mut Vec<A2lError>,
) {
let line = compu_method.get_line();
let uses_table = compu_method.conversion_type == ConversionType::TabIntp
|| compu_method.conversion_type == ConversionType::TabNointp
|| compu_method.conversion_type == ConversionType::TabVerb;
if uses_table && compu_method.compu_tab_ref.is_none() {
log_msgs.push(A2lError::ContentError {
item_name: compu_method.name.to_string(),
blockname: "COMPU_METHOD".to_string(),
line,
description: format!(
"COMPU_METHOD with conversion type {} must have a COMPU_TAB_REF.",
compu_method.conversion_type
),
});
} else if !uses_table && compu_method.compu_tab_ref.is_some() {
log_msgs.push(A2lError::ContentError {
item_name: compu_method.name.to_string(),
blockname: "COMPU_METHOD".to_string(),
line,
description: format!(
"COMPU_METHOD with conversion type {} must not have a COMPU_TAB_REF.",
compu_method.conversion_type
),
});
}
if compu_method.conversion_type == ConversionType::Linear
&& compu_method.coeffs_linear.is_none()
{
log_msgs.push(A2lError::ContentError {
item_name: compu_method.name.to_string(),
blockname: "COMPU_METHOD".to_string(),
line,
description: "COMPU_METHOD with conversion type LINEAR must have COEFFS_LINEAR."
.to_string(),
});
} else if compu_method.conversion_type != ConversionType::Linear
&& compu_method.coeffs_linear.is_some()
{
log_msgs.push(A2lError::ContentError {
item_name: compu_method.name.to_string(),
blockname: "COMPU_METHOD".to_string(),
line,
description: format!(
"COMPU_METHOD with conversion type {} must not have COEFFS_LINEAR.",
compu_method.conversion_type
),
});
}
if compu_method.conversion_type == ConversionType::RatFunc && compu_method.coeffs.is_none() {
log_msgs.push(A2lError::ContentError {
item_name: compu_method.name.to_string(),
blockname: "COMPU_METHOD".to_string(),
line,
description: "COMPU_METHOD with conversion type RATIONAL must have COEFFS.".to_string(),
});
} else if compu_method.conversion_type != ConversionType::RatFunc
&& compu_method.coeffs.is_some()
{
log_msgs.push(A2lError::ContentError {
item_name: compu_method.name.to_string(),
blockname: "COMPU_METHOD".to_string(),
line,
description: format!(
"COMPU_METHOD with conversion type {} must not have COEFFS.",
compu_method.conversion_type
),
});
}
if compu_method.conversion_type == ConversionType::Form && compu_method.formula.is_none() {
log_msgs.push(A2lError::ContentError {
item_name: compu_method.name.to_string(),
blockname: "COMPU_METHOD".to_string(),
line,
description: "COMPU_METHOD with conversion type FORM must have FORMULA.".to_string(),
});
} else if compu_method.conversion_type != ConversionType::Form && compu_method.formula.is_some()
{
log_msgs.push(A2lError::ContentError {
item_name: compu_method.name.to_string(),
blockname: "COMPU_METHOD".to_string(),
line,
description: format!(
"COMPU_METHOD with conversion type {} must not have FORMULA.",
compu_method.conversion_type
),
});
}
if let Some(compu_tab_ref) = &compu_method.compu_tab_ref
&& !compu_tabs.contains_key(&compu_tab_ref.conversion_table)
{
log_msgs.push(A2lError::CrossReferenceError {
source_type: "COMPU_METHOD".to_string(),
source_name: compu_method.name.to_string(),
source_line: line,
target_type: "COMPU_TAB".to_string(),
target_name: compu_tab_ref.conversion_table.to_string(),
});
}
if let Some(ref_unit) = &compu_method.ref_unit
&& !module.unit.contains_key(&ref_unit.unit)
{
log_msgs.push(A2lError::CrossReferenceError {
source_type: "COMPU_METHOD".to_string(),
source_name: compu_method.name.to_string(),
source_line: line,
target_type: "UNIT".to_string(),
target_name: ref_unit.unit.to_string(),
});
}
if let Some(status_string_ref) = &compu_method.status_string_ref
&& !compu_tabs.contains_key(&status_string_ref.conversion_table)
{
log_msgs.push(A2lError::CrossReferenceError {
source_type: "COMPU_METHOD".to_string(),
source_name: compu_method.name.to_string(),
source_line: line,
target_type: "COMPU_VTAB".to_string(),
target_name: status_string_ref.conversion_table.to_string(),
});
}
}
fn check_function(
function: &Function,
module: &Module,
objects: &ItemList<AnyObject>,
log_msgs: &mut Vec<A2lError>,
) {
if let Some(in_measurement) = &function.in_measurement {
let line = in_measurement.get_line();
check_reference_list(
"IN_MEASUREMENT",
"MEASUREMENT",
line,
&in_measurement.identifier_list,
Some(module),
objects,
log_msgs,
);
}
if let Some(loc_measurement) = &function.loc_measurement {
let line = loc_measurement.get_line();
check_reference_list(
"LOC_MEASUREMENT",
"MEASUREMENT",
line,
&loc_measurement.identifier_list,
Some(module),
objects,
log_msgs,
);
}
if let Some(out_measurement) = &function.out_measurement {
let line = out_measurement.get_line();
check_reference_list(
"OUT_MEASUREMENT",
"MEASUREMENT",
line,
&out_measurement.identifier_list,
Some(module),
objects,
log_msgs,
);
}
if let Some(def_characteristic) = &function.def_characteristic {
let line = def_characteristic.get_line();
check_reference_list(
"DEF_CHARACTERISTIC",
"CHARACTERISTIC",
line,
&def_characteristic.identifier_list,
Some(module),
objects,
log_msgs,
);
}
if let Some(ref_characteristic) = &function.ref_characteristic {
let line = ref_characteristic.get_line();
check_reference_list(
"REF_CHARACTERISTIC",
"CHARACTERISTIC",
line,
&ref_characteristic.identifier_list,
Some(module),
objects,
log_msgs,
);
}
if let Some(sub_function) = &function.sub_function {
let line = sub_function.get_line();
check_reference_list(
"SUB_FUNCTION",
"FUNCTION",
line,
&sub_function.identifier_list,
None, &module.function,
log_msgs,
);
}
}
fn check_function_list(
opt_function_list: &Option<FunctionList>,
module: &Module,
log_msgs: &mut Vec<A2lError>,
) {
if let Some(function_list) = opt_function_list {
let line = function_list.get_line();
check_reference_list(
"FUNCTION_LIST",
"FUNCTION",
line,
&function_list.name_list,
None, &module.function,
log_msgs,
);
}
}
fn check_group(
group: &Group,
module: &Module,
objects: &ItemList<AnyObject>,
log_msgs: &mut Vec<A2lError>,
) {
if let Some(ref_characteristic) = &group.ref_characteristic {
let line = ref_characteristic.get_line();
check_reference_list(
"REF_CHARACTERISTIC",
"CHARACTERISTIC",
line,
&ref_characteristic.identifier_list,
Some(module),
objects,
log_msgs,
);
}
if let Some(ref_measurement) = &group.ref_measurement {
let line = ref_measurement.get_line();
check_reference_list(
"REF_MEASUREMENT",
"MEASUREMENT",
line,
&ref_measurement.identifier_list,
Some(module),
objects,
log_msgs,
);
}
if let Some(function_list) = &group.function_list {
let line = function_list.get_line();
check_reference_list(
"FUNCTION_LIST",
"FUNCTION",
line,
&function_list.name_list,
None, &module.function,
log_msgs,
);
}
if let Some(sub_group) = &group.sub_group {
let line = sub_group.get_line();
check_reference_list(
"SUB_GROUP",
"GROUP",
line,
&sub_group.identifier_list,
None, &module.group,
log_msgs,
);
}
}
fn check_measurement(measurement: &Measurement, module: &Module, log_msgs: &mut Vec<A2lError>) {
let name = &measurement.name;
let line = measurement.get_line();
if measurement.conversion != "NO_COMPU_METHOD"
&& !module.compu_method.contains_key(&measurement.conversion)
{
log_msgs.push(A2lError::CrossReferenceError {
source_type: "MEASUREMENT".to_string(),
source_name: name.to_string(),
source_line: line,
target_type: "COMPU_METHOD".to_string(),
target_name: measurement.conversion.to_string(),
});
}
let opt_compu_method = module.compu_method.get(&measurement.conversion);
let calculated_limits = calc_compu_method_limits(opt_compu_method, measurement.datatype);
let existing_limits = (measurement.lower_limit, measurement.upper_limit);
if !check_limits_valid(existing_limits, calculated_limits) {
log_msgs.push(A2lError::LimitCheckError {
item_name: name.to_string(),
blockname: "MEASUREMENT".to_string(),
line,
lower_limit: measurement.lower_limit,
upper_limit: measurement.upper_limit,
calculated_lower_limit: calculated_limits.0,
calculated_upper_limit: calculated_limits.1,
});
}
check_ref_memory_segment(&measurement.ref_memory_segment, module, log_msgs);
check_function_list(&measurement.function_list, module, log_msgs);
}
fn check_typedef_measurement(
t_measurement: &TypedefMeasurement,
module: &Module,
log_msgs: &mut Vec<A2lError>,
) {
let name = &t_measurement.name;
let line = t_measurement.get_line();
if t_measurement.conversion != "NO_COMPU_METHOD"
&& !module.compu_method.contains_key(&t_measurement.conversion)
{
log_msgs.push(A2lError::CrossReferenceError {
source_type: "TYPEDEF_MEASUREMENT".to_string(),
source_name: name.to_string(),
source_line: line,
target_type: "COMPU_METHOD".to_string(),
target_name: t_measurement.conversion.to_string(),
});
}
let opt_compu_method = module.compu_method.get(&t_measurement.conversion);
let (lower_limit, upper_limit) =
calc_compu_method_limits(opt_compu_method, t_measurement.datatype);
if lower_limit > t_measurement.lower_limit || upper_limit < t_measurement.upper_limit {
log_msgs.push(A2lError::LimitCheckError {
item_name: name.to_string(),
blockname: "TYPEDEF_MEASUREMENT".to_string(),
line,
lower_limit: t_measurement.lower_limit,
upper_limit: t_measurement.upper_limit,
calculated_lower_limit: lower_limit,
calculated_upper_limit: upper_limit,
});
}
}
fn check_transformer(
transformer: &Transformer,
module: &Module,
objects: &ItemList<AnyObject>,
log_msgs: &mut Vec<A2lError>,
) {
let name = &transformer.name;
let line = transformer.get_line();
if transformer.inverse_transformer != "NO_INVERSE_TRANSFORMER"
&& !module
.transformer
.contains_key(&transformer.inverse_transformer)
{
log_msgs.push(A2lError::CrossReferenceError {
source_type: "TRANSFORMER".to_string(),
source_name: name.to_string(),
source_line: line,
target_type: "inverse TRANSFORMER".to_string(),
target_name: transformer.inverse_transformer.to_string(),
});
}
if let Some(transformer_in_objects) = &transformer.transformer_in_objects {
let line = transformer_in_objects.get_line();
check_reference_list(
"TRANSFORMER_IN_OBJECTS",
"CHARACTERISTIC / BLOB / INSTANCE",
line,
&transformer_in_objects.identifier_list,
Some(module),
objects,
log_msgs,
);
}
if let Some(transformer_out_objects) = &transformer.transformer_out_objects {
let line = transformer_out_objects.get_line();
check_reference_list(
"TRANSFORMER_OUT_OBJECTS",
"CHARACTERISTIC / BLOB / INSTANCE",
line,
&transformer_out_objects.identifier_list,
Some(module),
objects,
log_msgs,
);
}
}
fn check_ref_memory_segment(
opt_ref_memory_segment: &Option<RefMemorySegment>,
module: &Module,
log_msgs: &mut Vec<A2lError>,
) {
if let Some(ref_memory_segment) = opt_ref_memory_segment {
let line = ref_memory_segment.get_line();
let valid_ref = module
.mod_par
.as_ref()
.map(|mod_par| {
mod_par
.memory_segment
.contains_key(&ref_memory_segment.name)
})
.unwrap_or(false);
if !valid_ref {
log_msgs.push(A2lError::CrossReferenceError {
source_type: "REF_MEMORY_SEGMENT".to_string(),
source_name: ref_memory_segment.name.to_string(),
source_line: line,
target_type: "MEMORY_SEGMENT".to_string(),
target_name: ref_memory_segment.name.to_string(),
});
}
}
}
fn check_reference_list<T: A2lObjectName>(
container_type: &str,
ref_type: &str,
line: u32,
identifier_list: &[String],
opt_module: Option<&Module>,
map: &ItemList<T>,
log_msgs: &mut Vec<A2lError>,
) {
for ident in identifier_list {
if map.get(ident).is_none()
&& opt_module
.and_then(|m| m.find_instance_component(ident))
.is_none()
{
log_msgs.push(A2lError::CrossReferenceError {
source_type: container_type.to_string(),
source_name: ident.to_string(),
source_line: line,
target_type: ref_type.to_string(),
target_name: ident.to_string(),
});
}
}
}
struct GroupInfo<'a> {
is_root: bool,
parents: Vec<&'a str>,
}
fn check_group_structure(grouplist: &ItemList<Group>, log_msgs: &mut Vec<A2lError>) {
let mut groupinfo: HashMap<String, GroupInfo> = HashMap::new();
for group in grouplist {
groupinfo.insert(
group.name.clone(),
GroupInfo {
is_root: group.root.is_some(),
parents: Vec::new(),
},
);
}
for group in grouplist {
if let Some(sub_group) = &group.sub_group {
for sg in &sub_group.identifier_list {
if let Some(gi) = groupinfo.get_mut(sg) {
gi.parents.push(&group.name);
} else {
log_msgs.push(A2lError::CrossReferenceError {
source_type: "GROUP".to_string(),
source_name: group.name.clone(),
source_line: group.get_line(),
target_type: "GROUP".to_string(),
target_name: sg.clone(),
});
}
}
}
}
for group in grouplist {
let gi = groupinfo
.get(&group.name)
.expect("all groups should be in the groupinfo map");
if gi.is_root && gi.parents.len() > 1 {
log_msgs.push(A2lError::GroupStructureError {
group_name: group.name.clone(),
line: group.get_line(),
description: format!(
"has the ROOT attribute, but is also referenced as a sub-group by GROUPs {}",
gi.parents.join(", ")
),
});
} else if gi.is_root && gi.parents.len() == 1 {
log_msgs.push(A2lError::GroupStructureError {
group_name: group.name.clone(),
line: group.get_line(),
description: format!(
"has the ROOT attribute, but is also referenced as a sub-group by GROUP {}",
gi.parents[0]
),
});
} else if !gi.is_root && gi.parents.len() > 1 {
log_msgs.push(A2lError::GroupStructureError {
group_name: group.name.clone(),
line: group.get_line(),
description: format!(
"is referenced as a sub-group by multiple groups: {}",
gi.parents.join(", ")
),
});
} else if !gi.is_root && gi.parents.is_empty() {
log_msgs.push(A2lError::GroupStructureError {
group_name: group.name.clone(),
line: group.get_line(),
description: "does not have the ROOT attribute, and is not referenced as a sub-group by any other group".to_string(),
});
}
}
}
fn check_instance(
instance: &Instance,
typedefs: &ItemList<AnyTypedef>,
log_msgs: &mut Vec<A2lError>,
) {
let name = &instance.name;
let line = instance.get_line();
if !typedefs.contains_key(&instance.type_ref) {
log_msgs.push(A2lError::CrossReferenceError {
source_type: "INSTANCE".to_string(),
source_name: name.to_string(),
source_line: line,
target_type: "TYPEDEF_<x>".to_string(),
target_name: instance.type_ref.to_string(),
});
}
}
fn check_typedef_structure(
typedef_structure: &TypedefStructure,
typedefs: &ItemList<AnyTypedef>,
log_msgs: &mut Vec<A2lError>,
) {
let name = &typedef_structure.name;
let line = typedef_structure.get_line();
for sc in &typedef_structure.structure_component {
if !typedefs.contains_key(&sc.component_type) {
log_msgs.push(A2lError::CrossReferenceError {
source_type: format!("STRUCTURE_COMPONENT {} of TYPEDEF_STRUCTURE", sc.name),
source_name: name.to_string(),
source_line: line,
target_type: "TYPEDEF_<x>".to_string(),
target_name: sc.component_type.clone(),
});
}
}
}
fn calc_compu_method_limits(
opt_compu_method: Option<&CompuMethod>,
datatype: DataType,
) -> (f64, f64) {
let (mut lower_limit, mut upper_limit) = get_datatype_limits(datatype);
if let Some(cm) = opt_compu_method {
match cm.conversion_type {
ConversionType::Form => {
lower_limit = f64::MIN;
upper_limit = f64::MAX;
}
ConversionType::Linear => {
if let Some(c) = &cm.coeffs_linear {
if c.a >= 0.0 {
lower_limit = c.a * lower_limit + c.b;
upper_limit = c.a * upper_limit + c.b;
} else {
upper_limit = c.a * lower_limit + c.b;
lower_limit = c.a * upper_limit + c.b;
}
}
}
ConversionType::RatFunc => {
if let Some(c) = &cm.coeffs {
if c.a == 0.0 && c.d == 0.0 && c.e == 0.0 && c.f != 0.0 {
let func = |y: f64| c.f * (y / c.b) - (c.c / c.b);
lower_limit = func(lower_limit);
upper_limit = func(upper_limit);
if lower_limit > upper_limit {
std::mem::swap(&mut lower_limit, &mut upper_limit);
}
} else {
lower_limit = f64::MIN;
upper_limit = f64::MAX;
}
}
}
ConversionType::Identical
| ConversionType::TabIntp
| ConversionType::TabNointp
| ConversionType::TabVerb => {
}
}
}
(lower_limit, upper_limit)
}
fn check_limits_valid(existing: (f64, f64), calculated: (f64, f64)) -> bool {
let epsilon_lower = (calculated.0 * 1E-6).abs();
let epsilon_upper = (calculated.1 * 1E-6).abs();
(calculated.0 - existing.0) <= epsilon_lower && (existing.1 - calculated.1) <= epsilon_upper
}
fn get_datatype_limits(a2l_datatype: DataType) -> (f64, f64) {
match a2l_datatype {
DataType::Ubyte => (0.0, 255.0),
DataType::Sbyte => (-128.0, 127.0),
DataType::Uword => (0.0, 65535.0),
DataType::Sword => (-32768.0, 32767.0),
DataType::Ulong => (0.0, 4294967295.0),
DataType::Slong => (-2147483648.0, 2147483647.0),
DataType::AUint64 => (0.0, 18446744073709551615.0),
DataType::AInt64 => (-9223372036854775808.0, 9223372036854775807.0),
DataType::Float16Ieee => (-6.5504e+4_f64, 6.5504e+4_f64),
DataType::Float32Ieee => (f32::MIN as f64, f32::MAX as f64),
DataType::Float64Ieee => (f64::MIN, f64::MAX),
}
}
impl CharacteristicWrapper<'_> {
fn name(&self) -> &str {
match self {
CharacteristicWrapper::Characteristic(c) => &c.name,
CharacteristicWrapper::TypedefCharacteristic(tc) => &tc.name,
}
}
fn line(&self) -> u32 {
match self {
CharacteristicWrapper::Characteristic(c) => c.get_line(),
CharacteristicWrapper::TypedefCharacteristic(tc) => tc.get_line(),
}
}
fn axis_descr(&self) -> &[AxisDescr] {
match self {
CharacteristicWrapper::Characteristic(c) => &c.axis_descr,
CharacteristicWrapper::TypedefCharacteristic(tc) => &tc.axis_descr,
}
}
fn map_list(&self) -> Option<&MapList> {
match self {
CharacteristicWrapper::Characteristic(c) => c.map_list.as_ref(),
CharacteristicWrapper::TypedefCharacteristic(_) => None,
}
}
fn conversion(&self) -> &str {
match self {
CharacteristicWrapper::Characteristic(c) => &c.conversion,
CharacteristicWrapper::TypedefCharacteristic(tc) => &tc.conversion,
}
}
fn record_layout(&self) -> &str {
match self {
CharacteristicWrapper::Characteristic(c) => &c.deposit,
CharacteristicWrapper::TypedefCharacteristic(tc) => &tc.record_layout,
}
}
fn characteristic_type(&self) -> CharacteristicType {
match self {
CharacteristicWrapper::Characteristic(c) => c.characteristic_type,
CharacteristicWrapper::TypedefCharacteristic(tc) => tc.characteristic_type,
}
}
fn lower_limit(&self) -> f64 {
match self {
CharacteristicWrapper::Characteristic(c) => c.lower_limit,
CharacteristicWrapper::TypedefCharacteristic(tc) => tc.lower_limit,
}
}
fn upper_limit(&self) -> f64 {
match self {
CharacteristicWrapper::Characteristic(c) => c.upper_limit,
CharacteristicWrapper::TypedefCharacteristic(tc) => tc.upper_limit,
}
}
fn kind(&self) -> &str {
match self {
CharacteristicWrapper::Characteristic(_) => "CHARACTERISTIC",
CharacteristicWrapper::TypedefCharacteristic(_) => "TYPEDEF_CHARACTERISTIC",
}
}
}
#[cfg(test)]
mod test {
use crate::*;
#[test]
fn check_axis_descr() {
static A2L_TEXT: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin CHARACTERISTIC c "" CURVE 0x1234 rl 0 NO_COMPU_METHOD 0.0 1.0
/begin AXIS_DESCR COM_AXIS input_quantity conversion 1 0 100
AXIS_PTS_REF axis_points
CURVE_AXIS_REF curve_axis
/end AXIS_DESCR
/end CHARACTERISTIC
/begin RECORD_LAYOUT rl
FNC_VALUES 0 FLOAT32_IEEE ROW_DIR DIRECT
/end RECORD_LAYOUT
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 5);
static A2L_TEXT2: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin CHARACTERISTIC c "" CURVE 0x1234 rl 0 NO_COMPU_METHOD 0.0 1.0
/begin AXIS_DESCR COM_AXIS NO_INPUT_QUANTITY NO_COMPU_METHOD 1 0 100
/end AXIS_DESCR
/end CHARACTERISTIC
/begin RECORD_LAYOUT rl
FNC_VALUES 0 FLOAT32_IEEE ROW_DIR DIRECT
/end RECORD_LAYOUT
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT2, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 1);
static A2L_TEXT3: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin CHARACTERISTIC c "" CURVE 0x1234 rl 0 NO_COMPU_METHOD 0.0 1.0
/begin AXIS_DESCR STD_AXIS meas cm 1 0 100
AXIS_PTS_REF axis_points
/end AXIS_DESCR
/end CHARACTERISTIC
/begin AXIS_PTS axis_points "" 0x1234 NO_INPUT_QUANTITY rl 0 NO_COMPU_METHOD 3 0.0 10.0
/end AXIS_PTS
/begin RECORD_LAYOUT rl
FNC_VALUES 0 FLOAT32_IEEE ROW_DIR DIRECT
AXIS_PTS_X 0 FLOAT32_IEEE INDEX_INCR DIRECT
/end RECORD_LAYOUT
/begin COMPU_METHOD cm "" IDENTICAL "%4.2" "unit" /end COMPU_METHOD
/begin MEASUREMENT meas "" FLOAT32_IEEE cm 1 1.0 0 100
/end MEASUREMENT
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT3, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 1);
static A2L_TEXT4: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin CHARACTERISTIC c "" CURVE 0x1234 rl 0 NO_COMPU_METHOD 0.0 1.0
/begin AXIS_DESCR STD_AXIS meas cm 1 0 100
/end AXIS_DESCR
/end CHARACTERISTIC
/begin RECORD_LAYOUT rl
FNC_VALUES 0 FLOAT32_IEEE ROW_DIR DIRECT
AXIS_PTS_X 1 FLOAT32_IEEE INDEX_INCR DIRECT
/end RECORD_LAYOUT
/begin COMPU_METHOD cm "" IDENTICAL "%4.2" "unit" /end COMPU_METHOD
/begin MEASUREMENT meas "" FLOAT32_IEEE cm 1 1.0 0 100
/end MEASUREMENT
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT4, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 0);
}
#[test]
fn check_typedef_axis() {
static A2L_TEXT: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin TYPEDEF_AXIS typedef_axis_name "" input_quantity record_layout 0 conversion 1 0 100
/end TYPEDEF_AXIS
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 3);
static A2L_TEXT2: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin TYPEDEF_AXIS typedef_axis_name "" meas rl 0 cm 1 0 100
/end TYPEDEF_AXIS
/begin MEASUREMENT meas "" FLOAT32_IEEE cm 1 1.0 0 100
/end MEASUREMENT
/begin RECORD_LAYOUT rl /end RECORD_LAYOUT
/begin COMPU_METHOD cm "" IDENTICAL "%4.2" "unit" /end COMPU_METHOD
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT2, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 0);
}
#[test]
fn check_axis_pts() {
static A2L_TEXT: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin AXIS_PTS axis_pts_name "" 0x1234 input_qty record_layout 0 conversion 3 0.0 10.0
/end AXIS_PTS
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 3);
static A2L_TEXT2: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin AXIS_PTS axis_pts_name "" 0x1234 NO_INPUT_QUANTITY rl 0 NO_COMPU_METHOD 3 0.0 10.0
/end AXIS_PTS
/begin RECORD_LAYOUT rl
/end RECORD_LAYOUT
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT2, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 1);
static A2L_TEXT3: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin AXIS_PTS axis_pts_name "" 0x1234 meas rl 0 cm 3 0.0 10.0
/end AXIS_PTS
/begin MEASUREMENT meas "" FLOAT32_IEEE cm 1 1.0 0 100
/end MEASUREMENT
/begin RECORD_LAYOUT rl
AXIS_PTS_X 0 FLOAT32_IEEE INDEX_INCR DIRECT
/end RECORD_LAYOUT
/begin COMPU_METHOD cm "" IDENTICAL "%4.2" "unit" /end COMPU_METHOD
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT3, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 0);
}
#[test]
fn check_characteristic() {
static A2L_TEXT: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin CHARACTERISTIC c "" VALUE 0x1234 rl 0 conversion 0.0 1.0
COMPARISON_QUANTITY comp_qty
REF_MEMORY_SEGMENT mem_seg
/begin DEPENDENT_CHARACTERISTIC "formula"
name
/end DEPENDENT_CHARACTERISTIC
/begin FUNCTION_LIST
name
/end FUNCTION_LIST
/begin MAP_LIST
name
/end MAP_LIST
/begin VIRTUAL_CHARACTERISTIC "virt_char"
name
/end VIRTUAL_CHARACTERISTIC
/end CHARACTERISTIC
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 8);
static A2L_TEXT2: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin CHARACTERISTIC c "" VALUE 0x1234 rl 0 NO_COMPU_METHOD 0.0 1.0
/end CHARACTERISTIC
/begin RECORD_LAYOUT rl
/end RECORD_LAYOUT
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT2, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 1);
static A2L_TEXT3: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin CHARACTERISTIC c "" VALUE 0x1234 rl 0 NO_COMPU_METHOD 0.0 1.0
/begin AXIS_DESCR STD_AXIS NO_INPUT_QUANTITY NO_COMPU_METHOD 1 0 100
/end AXIS_DESCR
/end CHARACTERISTIC
/begin CHARACTERISTIC c2 "" MAP 0x1234 rl 0 NO_COMPU_METHOD 0.0 1.0
/end CHARACTERISTIC
/begin RECORD_LAYOUT rl
FNC_VALUES 0 FLOAT32_IEEE ROW_DIR DIRECT
AXIS_PTS_X 1 FLOAT32_IEEE INDEX_INCR DIRECT
/end RECORD_LAYOUT
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT3, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 2);
static A2L_TEXT4: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin CHARACTERISTIC c "" VALUE 0x1234 rl 0 cm 0.0 1.0
/end CHARACTERISTIC
/begin RECORD_LAYOUT rl
FNC_VALUES 0 FLOAT32_IEEE ROW_DIR DIRECT
/end RECORD_LAYOUT
/begin COMPU_METHOD cm "" IDENTICAL "%4.2" "unit" /end COMPU_METHOD
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT4, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 0);
}
#[test]
fn check_typedef_characteristic() {
static A2L_TEXT: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin TYPEDEF_CHARACTERISTIC name "" CURVE record_layout 0 conversion 0 100
/begin AXIS_DESCR STD_AXIS input_quantity conversion 1 0 100
/end AXIS_DESCR
/end TYPEDEF_CHARACTERISTIC
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 4);
static A2L_TEXT2: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin TYPEDEF_CHARACTERISTIC name "" CUBOID rl 0 NO_COMPU_METHOD 0 100
/end TYPEDEF_CHARACTERISTIC
/begin RECORD_LAYOUT rl
FNC_VALUES 0 FLOAT32_IEEE ROW_DIR DIRECT
/end RECORD_LAYOUT
/begin COMPU_METHOD cm "" IDENTICAL "%4.2" "unit" /end COMPU_METHOD
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT2, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 1);
static A2L_TEXT3: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin TYPEDEF_CHARACTERISTIC name "" VALUE rl 0 cm 0 100
/end TYPEDEF_CHARACTERISTIC
/begin RECORD_LAYOUT rl
FNC_VALUES 0 FLOAT32_IEEE ROW_DIR DIRECT
/end RECORD_LAYOUT
/begin COMPU_METHOD cm "" IDENTICAL "%4.2" "unit" /end COMPU_METHOD
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT3, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 0);
}
#[test]
fn check_compu_method() {
static A2L_TEXT: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin COMPU_METHOD cm "" TAB_VERB "%4.2" "unit"
COMPU_TAB_REF compu_tab_ref
REF_UNIT ref_unit
STATUS_STRING_REF status_string_ref
/end COMPU_METHOD
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 3);
static A2L_TEXT2: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin COMPU_METHOD cm "" IDENTICAL "%4.2" "unit"
/end COMPU_METHOD
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT2, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 0);
}
#[test]
fn check_function() {
static A2L_TEXT: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin FUNCTION f ""
/begin IN_MEASUREMENT
name
/end IN_MEASUREMENT
/begin LOC_MEASUREMENT
name
/end LOC_MEASUREMENT
/begin OUT_MEASUREMENT
name
/end OUT_MEASUREMENT
/begin DEF_CHARACTERISTIC
name
/end DEF_CHARACTERISTIC
/begin REF_CHARACTERISTIC
name
/end REF_CHARACTERISTIC
/begin SUB_FUNCTION
name
/end SUB_FUNCTION
/end FUNCTION
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 6);
static A2L_TEXT2: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin FUNCTION f ""
/end FUNCTION
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT2, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 0);
}
#[test]
fn check_group() {
static A2L_TEXT: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin GROUP g ""
/begin REF_CHARACTERISTIC
name
/end REF_CHARACTERISTIC
/begin REF_MEASUREMENT
name
/end REF_MEASUREMENT
/begin FUNCTION_LIST
name
/end FUNCTION_LIST
/begin SUB_GROUP
name
/end SUB_GROUP
/end GROUP
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 6);
}
#[test]
fn check_group_structure() {
static A2L_TEXT: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin GROUP grp1 "" ROOT
/end GROUP
/begin GROUP grp2 "" ROOT
/begin SUB_GROUP
grp1
/end SUB_GROUP
/end GROUP
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 1);
static A2L_TEXT2: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin GROUP grp1 "" ROOT
/end GROUP
/begin GROUP grp2 "" ROOT
/begin SUB_GROUP
grp1
/end SUB_GROUP
/end GROUP
/begin GROUP grp3 "" ROOT
/begin SUB_GROUP
grp1
/end SUB_GROUP
/end GROUP
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT2, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 1);
static A2L_TEXT3: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin GROUP grp1 ""
/end GROUP
/begin GROUP grp2 "" ROOT
/begin SUB_GROUP
grp1
/end SUB_GROUP
/end GROUP
/begin GROUP grp3 "" ROOT
/begin SUB_GROUP
grp1
/end SUB_GROUP
/end GROUP
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT3, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 1);
}
#[test]
fn check_measurement() {
static A2L_TEXT: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin MEASUREMENT m "" FLOAT32_IEEE conversion 1 1.0 0 100
REF_MEMORY_SEGMENT mem_seg
/begin FUNCTION_LIST
name
/end FUNCTION_LIST
/end MEASUREMENT
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 3);
}
#[test]
fn check_typedef_measurement() {
static A2L_TEXT: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin TYPEDEF_MEASUREMENT tm "" UBYTE conversion 1 1 0 100
/end TYPEDEF_MEASUREMENT
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 1);
}
#[test]
fn check_transformer() {
static A2L_TEXT: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin TRANSFORMER transformer_name "version string" "dll32" "dll64" 1 ON_CHANGE inverse_transformer
/begin TRANSFORMER_IN_OBJECTS
name
/end TRANSFORMER_IN_OBJECTS
/begin TRANSFORMER_OUT_OBJECTS
name
/end TRANSFORMER_OUT_OBJECTS
/end TRANSFORMER
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 3);
static A2L_TEXT2: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin TRANSFORMER transformer_name "version string" "dll32" "dll64" 1 ON_CHANGE NO_INVERSE_TRANSFORMER
/end TRANSFORMER
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT2, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 0);
}
#[test]
fn check_instance() {
static A2L_TEXT: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin INSTANCE i "" type_ref 0x1234
/end INSTANCE
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 1);
}
#[test]
fn check_typedef_structure() {
static A2L_TEXT: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin TYPEDEF_STRUCTURE ts "" 1
/begin STRUCTURE_COMPONENT component_name component_type 1
/end STRUCTURE_COMPONENT
/end TYPEDEF_STRUCTURE
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 1);
}
#[test]
fn check_calc_limits() {
let cm = CompuMethod::new(
"".to_string(),
"".to_string(),
ConversionType::Form,
"format".to_string(),
"unit".to_string(),
);
let datatype = DataType::Ubyte;
let (lower_limit, upper_limit) = super::calc_compu_method_limits(Some(&cm), datatype);
assert_eq!(lower_limit, f64::MIN);
assert_eq!(upper_limit, f64::MAX);
let mut cm = CompuMethod::new(
"".to_string(),
"".to_string(),
ConversionType::Linear,
"format".to_string(),
"unit".to_string(),
);
cm.coeffs_linear = Some(CoeffsLinear::new(33.0, 2.0));
let datatype = DataType::Sbyte;
let (lower_limit, upper_limit) = super::calc_compu_method_limits(Some(&cm), datatype);
assert_eq!(lower_limit, 33.0 * -128.0 + 2.0);
assert_eq!(upper_limit, 33.0 * 127.0 + 2.0);
let mut cm = CompuMethod::new(
"".to_string(),
"".to_string(),
ConversionType::RatFunc,
"format".to_string(),
"unit".to_string(),
);
cm.coeffs = Some(Coeffs::new(0.0, 3.5, 4.44, 0.0, 0.0, 2.0));
let datatype = DataType::Slong;
let (lower_limit, upper_limit) = super::calc_compu_method_limits(Some(&cm), datatype);
assert_eq!(lower_limit, (2.0 * -2147483648.0 - 4.44) / 3.5);
assert_eq!(upper_limit, (2.0 * 2147483647.0 - 4.44) / 3.5);
let mut cm = CompuMethod::new(
"".to_string(),
"".to_string(),
ConversionType::RatFunc,
"format".to_string(),
"unit".to_string(),
);
cm.coeffs = Some(Coeffs::new(0.0, -3.5, 4.44, 0.0, 0.0, 2.0));
let datatype = DataType::Float64Ieee;
let (lower_limit, upper_limit) = super::calc_compu_method_limits(Some(&cm), datatype);
assert_ne!(lower_limit, f64::MIN);
assert_ne!(upper_limit, f64::MAX);
assert_ne!(lower_limit, -f64::INFINITY);
assert_ne!(upper_limit, f64::INFINITY);
let mut cm = CompuMethod::new(
"".to_string(),
"".to_string(),
ConversionType::RatFunc,
"format".to_string(),
"unit".to_string(),
);
cm.coeffs = Some(Coeffs::new(1.0, 3.5, 4.44, 2.0, 3.0, 4.0));
let datatype = DataType::Slong;
let (lower_limit, upper_limit) = super::calc_compu_method_limits(Some(&cm), datatype);
assert_eq!(lower_limit, f64::MIN);
assert_eq!(upper_limit, f64::MAX);
let cm = CompuMethod::new(
"".to_string(),
"".to_string(),
ConversionType::Identical,
"format".to_string(),
"unit".to_string(),
);
let datatype = DataType::Float32Ieee;
let (lower_limit, upper_limit) = super::calc_compu_method_limits(Some(&cm), datatype);
assert_eq!(lower_limit, f32::MIN as f64);
assert_eq!(upper_limit, f32::MAX as f64);
}
#[test]
fn check_limits() {
static A2L_TEXT: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin CHARACTERISTIC c "" CURVE 0x1234 rl 0 cm 0 100000
/begin AXIS_DESCR STD_AXIS NO_INPUT_QUANTITY NO_COMPU_METHOD 1 -1E39 1E39
/end AXIS_DESCR
/end CHARACTERISTIC
/begin AXIS_PTS axis_points "" 0x1234 NO_INPUT_QUANTITY rl2 0 cm2 3 -100000.0 100000.0
/end AXIS_PTS
/begin MEASUREMENT m "" ULONG cm3 1 1.0 -1000000 1000000
/end MEASUREMENT
/begin RECORD_LAYOUT rl
FNC_VALUES 0 UWORD ROW_DIR DIRECT
AXIS_PTS_X 0 FLOAT32_IEEE INDEX_INCR DIRECT
/end RECORD_LAYOUT
/begin RECORD_LAYOUT rl2
AXIS_PTS_X 0 SWORD INDEX_INCR DIRECT
/end RECORD_LAYOUT
/begin COMPU_METHOD cm "" LINEAR "%4.2" "unit"
COEFFS_LINEAR 0.5 0
/end COMPU_METHOD
/begin COMPU_METHOD cm2 "" RAT_FUNC "%4.2" "unit"
COEFFS 0 500 4000 0 0 2.222
/end COMPU_METHOD
/begin COMPU_METHOD cm3 "" IDENTICAL "%4.2" "unit"
/end COMPU_METHOD
/end MODULE /end PROJECT"#;
let (mut a2lfile, _) = load_from_string(A2L_TEXT, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 4);
a2lfile.project.module[0].characteristic[0].upper_limit = u16::MAX as f64 * 0.5;
a2lfile.project.module[0].characteristic[0].axis_descr[0].upper_limit = f32::MAX as f64;
a2lfile.project.module[0].characteristic[0].axis_descr[0].lower_limit = f32::MIN as f64;
a2lfile.project.module[0].axis_pts[0].upper_limit =
((i16::MAX as f64 * 2.222) - 4000.0) / 500.0;
a2lfile.project.module[0].axis_pts[0].lower_limit =
((i16::MIN as f64 * 2.222) - 4000.0) / 500.0;
a2lfile.project.module[0].measurement[0].lower_limit = u32::MIN as f64;
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 0);
}
#[test]
fn axis_pts_with_this() {
static A2L_TEXT: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin TYPEDEF_AXIS lookup_axis_type "LookUpTable axis" NO_INPUT_QUANTITY F32 0 NO_COMPU_METHOD 16 -1E32 1E32
/end TYPEDEF_AXIS
/begin TYPEDEF_CHARACTERISTIC lookup_values_type "LookUpTable values" MAP F32 0 NO_COMPU_METHOD -1E32 1E32
/begin AXIS_DESCR COM_AXIS NO_INPUT_QUANTITY NO_COMPU_METHOD 16 0.0 0.0
AXIS_PTS_REF THIS.lookup_axis
/end AXIS_DESCR
/begin AXIS_DESCR CURVE_AXIS NO_INPUT_QUANTITY NO_COMPU_METHOD 16 0.0 0.0
CURVE_AXIS_REF THIS.lookup_axis
/end AXIS_DESCR
/end TYPEDEF_CHARACTERISTIC
/begin TYPEDEF_STRUCTURE LookUpTable "" 128 CONSISTENT_EXCHANGE
/begin STRUCTURE_COMPONENT lookup_axis lookup_axis_type 0 /end STRUCTURE_COMPONENT
/begin STRUCTURE_COMPONENT lookup_values lookup_values_type 64 /end STRUCTURE_COMPONENT
/end TYPEDEF_STRUCTURE
/begin INSTANCE lockup_table "" LookUpTable 0x80010000 /end INSTANCE
/begin RECORD_LAYOUT F32
FNC_VALUES 0 FLOAT32_IEEE ROW_DIR DIRECT
/end RECORD_LAYOUT
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 0);
static A2L_TEXT2: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin TYPEDEF_CHARACTERISTIC lookup_values_type "LookUpTable values" CURVE F32 0 NO_COMPU_METHOD -1E32 1E32
/begin AXIS_DESCR COM_AXIS NO_INPUT_QUANTITY NO_COMPU_METHOD 16 0.0 0.0
AXIS_PTS_REF THIS.lookup_axis
CURVE_AXIS_REF THIS.lookup_axis
/end AXIS_DESCR
/end TYPEDEF_CHARACTERISTIC
/begin TYPEDEF_STRUCTURE LookUpTable "" 128 CONSISTENT_EXCHANGE
/begin STRUCTURE_COMPONENT lookup_values lookup_values_type 64 /end STRUCTURE_COMPONENT
/end TYPEDEF_STRUCTURE
/begin INSTANCE lockup_table "" LookUpTable 0x80010000 /end INSTANCE
/begin RECORD_LAYOUT F32
FNC_VALUES 0 FLOAT32_IEEE ROW_DIR DIRECT
/end RECORD_LAYOUT
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT2, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 3);
}
#[test]
fn check_instance_references() {
static A2L_TEXT2: &str = r#"ASAP2_VERSION 1 71 /begin PROJECT p "" /begin MODULE m ""
/begin TYPEDEF_MEASUREMENT td_measurement "" UBYTE NO_COMPU_METHOD 1 1 0 100
/end TYPEDEF_MEASUREMENT
/begin TYPEDEF_STRUCTURE td_struct "" 128 CONSISTENT_EXCHANGE
/begin STRUCTURE_COMPONENT item td_measurement 1 /end STRUCTURE_COMPONENT
/end TYPEDEF_STRUCTURE
/begin INSTANCE inst "" td_struct 0x80010000 /end INSTANCE
/begin RECORD_LAYOUT F32
FNC_VALUES 0 FLOAT32_IEEE ROW_DIR DIRECT
/end RECORD_LAYOUT
/begin CHARACTERISTIC char "" CURVE 0x1234 rl 0 NO_COMPU_METHOD 0.0 1.0
/begin AXIS_DESCR STD_AXIS inst.item NO_COMPU_METHOD 1 0 100
/end AXIS_DESCR
/end CHARACTERISTIC
/begin RECORD_LAYOUT rl
FNC_VALUES 0 FLOAT32_IEEE ROW_DIR DIRECT
AXIS_PTS_X 1 FLOAT32_IEEE INDEX_INCR DIRECT
/end RECORD_LAYOUT
/end MODULE /end PROJECT"#;
let (a2lfile, _) = load_from_string(A2L_TEXT2, None, true).unwrap();
let log_msgs = super::check(&a2lfile);
assert_eq!(log_msgs.len(), 0);
}
}