#![deny(missing_docs)]
extern crate bidir_map;
extern crate indexmap;
extern crate snafu;
extern crate xio_base_datatypes;
extern crate xio_hwdb;
extern crate xio_instructionset;
extern crate xio_jobset;
mod error;
pub use error::{CodeLocation, Error};
use base::{HasDataType, HasStorageType, IsInStorageType};
use bidir_map::BidirMap;
use indexmap::{IndexMap, IndexSet};
use snafu::{ensure, OptionExt, ResultExt};
use std::ops::Shl;
use xio_base_datatypes as base;
use xio_hwdb as hwdb;
use xio_instructionset as instructionset;
use xio_jobset as jobset;
type Result<T> = std::result::Result<T, error::Error>;
#[derive(Debug)]
pub struct CompiledJobSet {
pub parameters: CompiledParameterSet,
pub job_mapping: BidirMap<String, u16>,
pub jobs: IndexMap<String, Vec<u16>>,
}
impl CompiledJobSet {
pub fn build_jobs(&self) -> Result<Vec<Vec<u16>>> {
Ok((0..self.jobs.len())
.map(|i| {
let name = self
.job_mapping
.get_by_second(&(i as u16))
.context(error::ProgrammerError {
msg: format!(
"Expected job at index {} not found",
i
),
})?
.to_string();
self.jobs
.get(&name)
.context(error::ProgrammerError {
msg: format!(
"Expected job at index {:?} not found",
name
),
})
.map(|v| v.clone())
})
.collect::<Result<Vec<_>>>()?)
}
}
#[derive(Debug)]
pub struct CompiledParameterSet {
pub parameter_mapping: BidirMap<String, u16>,
pub parameters: IndexMap<String, base::ParameterValueRaw>,
pub runtime_parameters: IndexMap<String, base::DataValueRaw>,
pub assigned_capabilities: IndexSet<String>,
}
impl CompiledParameterSet {
pub fn build_request_parameters(
&self,
) -> Result<Vec<base::ParameterValueRaw>> {
Ok((0..self.parameter_mapping.len())
.map(|i| {
let name = self
.parameter_mapping
.get_by_second(&(i as u16))
.context(error::ProgrammerError {
msg: format!(
"Expected parameter at index {} not found",
i
),
})?
.to_string();
self.parameters
.get(&name)
.context(error::MappedParameterNotFound {
name: name.to_string(),
})
.map(|v| v.clone())
})
.collect::<Result<Vec<_>>>()?)
}
pub fn build_runtime_parameters(
&self,
) -> Result<IndexMap<u16, base::DataValueRaw>> {
Ok(self
.runtime_parameters
.iter()
.map(|(k, v)| {
let index = self
.parameter_mapping
.get_by_first(&k.to_string())
.context(error::ProgrammerError {
msg: format!(
"Runtime parameter at index {} not found",
k
),
})?;
Ok((*index, v.clone()))
})
.collect::<Result<IndexMap<u16, base::DataValueRaw>>>()?)
}
}
#[derive(Debug)]
pub struct HardwareTarget {
pub id: String,
pub description: hwdb::HardwareBoardDescription,
pub modules: IndexMap<String, hwdb::Module>,
pub instructions: instructionset::InstructionMap,
}
impl HardwareTarget {
fn channel_reference(
&self,
name: &str,
assignment: &jobset::ChannelAssignment,
hardware_board: &str,
required_datatype: &base::DataType,
) -> Result<(base::ParameterValueRaw, String)> {
let assigned_capability = assignment.capability.to_string();
let capability = self
.description
.capabilities
.get(&assigned_capability)
.context(error::CapabilityNotFound {
name: assignment.capability.to_string(),
parameter_name: name.to_string(),
hardware_board: hardware_board.to_string(),
})?;
let module = self.modules.get(&capability.module).context(
error::ModuleDescriptionNotFound {
name: capability.module.to_string(),
parameter_name: name.to_string(),
capability_name: assignment.capability.to_string(),
hardware_board: hardware_board.to_string(),
},
)?;
let channel_index = module
.channels
.iter()
.position(|c| c.id == assignment.channel)
.context(error::ModuleChannelNotFound {
name: assignment.channel.to_string(),
module_name: capability.module.to_string(),
parameter_name: name.to_string(),
})?;
let channel = &module.channels[channel_index];
ensure!(
channel.channel_type == *required_datatype,
error::ChannelDataTypeMismatch {
index: channel_index as u16,
name: assignment.channel.to_string(),
module_name: capability.module.to_string(),
required_datatype: required_datatype.clone(),
found_datatype: channel.channel_type.clone(),
parameter_name: name.to_string(),
}
);
let reference = base::ModuleChannelReference {
module_id: capability.id,
channel_id: channel_index as u16,
};
use base::DataType::*;
use base::ParameterValue::{
BooleanChannel, Int16Channel, Int32Channel, Int64Channel,
Int8Channel, ParameterMaskChannel, UInt16Channel,
UInt32Channel, UInt64Channel, UInt8Channel,
};
match channel.channel_type {
Boolean => {
Ok((BooleanChannel(reference), assigned_capability))
}
Int8 => Ok((Int8Channel(reference), assigned_capability)),
Int16 => Ok((Int16Channel(reference), assigned_capability)),
Int32 => Ok((Int32Channel(reference), assigned_capability)),
Int64 => Ok((Int64Channel(reference), assigned_capability)),
UInt8 => Ok((UInt8Channel(reference), assigned_capability)),
UInt16 => Ok((UInt16Channel(reference), assigned_capability)),
UInt32 => Ok((UInt32Channel(reference), assigned_capability)),
UInt64 => Ok((UInt64Channel(reference), assigned_capability)),
ParameterMask => {
Ok((ParameterMaskChannel(reference), assigned_capability))
}
Invalid => error::InvalidDataType {
name: name.to_string(),
}
.fail()?,
}
}
}
pub trait XioJobSetCompile {
fn compile(&self, target: &HardwareTarget) -> Result<CompiledJobSet>;
}
impl XioJobSetCompile for jobset::JobSet {
fn compile(&self, target: &HardwareTarget) -> Result<CompiledJobSet> {
let compiled_parameters =
compile_parameters(&self.parameters, target, &self.channels)?;
compile_jobs(&self.jobs, compiled_parameters, &target.instructions)
}
}
struct ParameterEntry {
description_layer: String,
description: jobset::ParameterDescription,
value: Option<base::DataValueDescriptive>,
}
#[derive(Default)]
struct ParameterBuilder {
entries: IndexMap<String, ParameterEntry>,
}
impl ParameterBuilder {
fn overlay(
mut self,
layer: &jobset::ParameterSet,
) -> Result<ParameterBuilder> {
for (ref k, ref v) in &layer.descriptions {
if let Some(d) = self.entries.get(&k.to_string()) {
error::ParameterAlreadyDescribed {
name: k.to_string(),
original_layer: d.description_layer.to_string(),
offending_layer: layer.name.to_string(),
}
.fail()?;
}
let description = (*v).clone();
{
use base::OverrideOption::*;
use base::StorageType::*;
match (
layer.values.contains_key(&k.to_string()),
&description.override_option,
&description.storage,
) {
(true, &Enforce, _) => {
error::ParameterOverrideEnforceNotRespected {
name: k.to_string(),
layer: layer.name.to_string(),
}
.fail()?;
}
(false, &Forbid, &Fixed) => {
error::ParameterOverrideForbidNotSatisfied {
name: k.to_string(),
layer: layer.name.to_string(),
}
.fail()?;
}
_ => {}
}
}
let entry = ParameterEntry {
description_layer: layer.name.to_string(),
description,
value: None,
};
self.entries.insert(k.to_string(), entry);
}
for (ref k, ref v) in &layer.values {
if let Some(d) = self.entries.get_mut(&k.to_string()) {
ensure!(
d.description.override_option
!= base::OverrideOption::Forbid
|| d.description_layer == layer.name,
error::ParameterOverrideNotAllowed {
name: k.to_string(),
layer: layer.name.to_string(),
}
);
use base::TryConvertTo;
let t = &d.description.data_type;
let value = v.try_convert_to(t).context(
error::ParameterTypeMismatch {
name: k.to_string(),
defined_type: (*t).clone(),
defined_layer: d.description_layer.clone(),
value: (**v).clone(),
value_layer: layer.name.to_string(),
},
)?;
d.value = Some(value);
} else {
error::ParameterNotYetDescribed {
name: k.to_string(),
layer: layer.name.to_string(),
}
.fail()?;
}
}
Ok(self)
}
fn finalize(
self,
target: &HardwareTarget,
mapping: &IndexMap<String, jobset::ChannelAssignment>,
) -> Result<CompiledParameterSet> {
let referenced_parameters = self
.entries
.iter()
.filter_map(|(_, v)| v.value.clone())
.filter_map(|v| {
if let base::DataValue::ParameterMask(ref list) = v {
Some(list.clone())
} else {
None
}
})
.flat_map(|list| list.into_iter())
.collect::<IndexSet<String>>();
let mut non_referenced_parameters = self
.entries
.keys()
.map(|v| v.to_string())
.collect::<IndexSet<String>>();
for p in &referenced_parameters {
ensure!(
non_referenced_parameters.take(p).is_some(),
error::MappedParameterNotFound {
name: p.to_string(),
}
);
}
let parameter_mapping = referenced_parameters
.into_iter()
.chain(non_referenced_parameters.into_iter())
.enumerate()
.map(|(i, v)| (v.to_string(), i as u16))
.collect::<BidirMap<String, u16>>();
let mut parameters = IndexMap::new();
let mut runtime_parameters = IndexMap::new();
let mut assigned_capabilities = IndexSet::new();
for (ref k, ref v) in &self.entries {
let (value, runtime_value) = match v.description.storage {
base::StorageType::Fixed => {
if let Some(ref v) = v.value {
(
base::ParameterValueRaw::from(
v.to_raw(¶meter_mapping)
.context(error::XioBaseDatatypes {})?,
),
None,
)
} else {
return error::ParameterWithFixedStorageAndNoValue {
name: k.to_string(),
}
.fail();
}
}
base::StorageType::Channel => {
let assignment = mapping.get(&k.to_string()).context(
error::ParameterNotMappedInHardwareAssignment {
name: k.to_string(),
hardware_board: target.id.to_string(),
},
)?;
let (reference, assigned_capability) = target
.channel_reference(
k,
assignment,
&target.id,
&v.description.data_type,
)?;
assigned_capabilities.insert(assigned_capability);
(
reference,
match v.value {
Some(ref v) => Some(
v.to_raw(¶meter_mapping)
.context(error::XioBaseDatatypes)?,
),
None => None,
},
)
}
};
parameters.insert(k.to_string(), value);
if let Some(r) = runtime_value {
runtime_parameters.insert(
k.to_string(),
r,
);
}
}
Ok(CompiledParameterSet {
parameter_mapping,
parameters,
runtime_parameters,
assigned_capabilities,
})
}
}
fn compile_parameters(
parameters: &[jobset::ParameterSet],
target: &HardwareTarget,
mapping: &IndexMap<String, jobset::ChannelAssignment>,
) -> Result<CompiledParameterSet> {
let mut builder = ParameterBuilder::default();
for layer in parameters {
builder = builder.overlay(&layer)?;
}
builder.finalize(target, mapping)
}
fn compile_jobs(
jobs: &IndexMap<String, xio_jobset::Job>,
parameters: CompiledParameterSet,
instructions: &instructionset::InstructionMap,
) -> Result<CompiledJobSet> {
let jobs = jobs
.iter()
.map(|(k, v)| {
Ok((
k.to_string(),
compile_job(v, ¶meters, instructions, k)?,
))
})
.collect::<Result<IndexMap<String, Vec<u16>>>>()?;
let job_mapping = jobs
.keys()
.enumerate()
.map(|(i, n)| (n.to_string(), i as u16))
.collect::<BidirMap<String, u16>>();
Ok(CompiledJobSet {
jobs,
job_mapping,
parameters,
})
}
fn compile_job(
job: &xio_jobset::Job,
parameters: &CompiledParameterSet,
instructions: &instructionset::InstructionMap,
job_name: &str,
) -> Result<Vec<u16>> {
let mut commands = Vec::new();
for (command_index, command) in job.commands.iter().enumerate() {
let command_index = command_index as u16;
let location = CodeLocation::Command {
job: job_name.to_string(),
command_index,
command_description: command.description.to_string(),
command_message: command.message.to_string(),
};
commands.extend(compile_command_with_conditions(
command,
parameters,
instructions,
&location,
)?);
}
Ok(commands)
}
fn compile_command_with_conditions(
command: &xio_jobset::Command,
parameters: &CompiledParameterSet,
instructions: &instructionset::InstructionMap,
location: &CodeLocation,
) -> Result<Vec<u16>> {
let instruction = instructions.get(&command.command_type).context(
error::InstructionNotFound {
name: command.command_type.to_string(),
location: location.clone(),
},
)?;
use instructionset::InstructionCategory::*;
match instruction.category() {
CommandWithoutTimeExtent if command.conditions.is_empty() => {
compile_command(command, parameters, instruction, location)
}
CommandWithoutTimeExtent => {
error::CommandWithoutTimeExtentButConditions {
location: location.clone(),
command_type: command.command_type.to_string(),
}
.fail()?
}
CommandWithTimeExtent if !command.conditions.is_empty() => {
let mut commands = compile_command(
command,
parameters,
instruction,
location,
)?;
commands.extend(compile_conditions(
&command.conditions,
parameters,
instructions,
location,
)?);
Ok(commands)
}
CommandWithTimeExtent => {
error::CommandWithTimeExtentButNoConditions {
location: location.clone(),
command_type: command.command_type.to_string(),
}
.fail()?
}
Condition => error::FoundConditionInCommands {
location: location.clone(),
command_type: command.command_type.to_string(),
}
.fail()?,
Invalid => error::InvalidInstructionCategory {
location: location.clone(),
command_type: command.command_type.to_string(),
}
.fail()?,
}
}
trait HasParameters {
fn get_parameters(&self) -> &IndexMap<String, String>;
}
trait HasFlags {
fn get_flags(&self) -> u8 {
0u8
}
}
impl HasParameters for jobset::Command {
fn get_parameters(&self) -> &IndexMap<String, String> {
&self.parameters
}
}
impl HasParameters for jobset::Condition {
fn get_parameters(&self) -> &IndexMap<String, String> {
&self.parameters
}
}
impl HasFlags for jobset::Command {}
impl HasFlags for jobset::Condition {
fn get_flags(&self) -> u8 {
if self.exit_job {
0b0001u8
} else {
0b0000u8
}
}
}
fn compile_command<T: HasParameters + HasFlags>(
command: &T,
parameters: &CompiledParameterSet,
instruction: &instructionset::Instruction,
location: &CodeLocation,
) -> Result<Vec<u16>> {
let mut param_groups = IndexMap::new();
let mut param_names = IndexSet::new();
for param in &instruction.parameters {
let group = param_groups
.entry(param.group.to_string())
.or_insert_with(IndexSet::new);
group.insert(param.id.to_string());
param_names.insert(param.id.to_string());
}
param_groups.remove(&"".to_string());
let effective_param_names = command
.get_parameters()
.keys()
.map(|k| k.to_string())
.collect::<IndexSet<String>>();
ensure!(
effective_param_names == param_names,
error::WrongParameters {
found: effective_param_names,
required: param_names,
location: Box::new(location.clone()),
}
);
for (group, params) in param_groups {
let params = params
.iter()
.map(|k| {
command.get_parameters().get(&k.to_string()).context(
error::ParameterNotFound {
name: k.to_string(),
},
)
})
.collect::<Result<Vec<_>>>()?
.into_iter()
.map(|k| {
Ok((
k.to_string(),
parameters.parameters.get(k).context(
error::ParameterNotFound {
name: k.to_string(),
},
)?,
))
})
.collect::<Result<IndexMap<String, &base::ParameterValueRaw>>>(
)?
.into_iter()
.map(|(k, v)| (k, v.data_type()))
.collect::<IndexMap<String, base::DataType>>();
ensure!(
params
.values()
.cloned()
.collect::<IndexSet<base::DataType>>()
.len()
== 1,
error::ParametersInGroupAreOfDifferentTypes {
parameters: params
.keys()
.map(|s| s.to_string())
.collect::<IndexSet<String>>(),
location: Box::new(location.clone()),
group,
}
);
}
let code = u16::from(command.get_flags()).shl(12) | instruction.code;
let mut chunks = vec![code];
for param in &instruction.parameters {
let id = ¶m.id;
let param_index = command
.get_parameters()
.get(id)
.context(error::InstructionParameterNotFound {
name: id.to_string(),
})?
.to_string();
let value = parameters
.parameters
.get(¶m_index)
.context(error::ParameterNotFound {
name: param_index.to_string(),
})?
.clone();
ensure!(
value.is_in_storage(¶m.storage),
error::ParameterStorageDoesNotFit {
name: param_index.to_string(),
location: location.clone(),
required_storage: param.storage,
found_storage: value.storage_type(),
}
);
let param_index = parameters
.parameter_mapping
.get_by_first(¶m_index)
.context(error::ParameterNotFound {
name: id.to_string(),
})?;
chunks.push(*param_index);
}
Ok(chunks)
}
fn compile_conditions(
conditions: &[xio_jobset::Condition],
parameters: &CompiledParameterSet,
instructions: &instructionset::InstructionMap,
location: &CodeLocation,
) -> Result<Vec<u16>> {
let mut commands = Vec::new();
for (condition_index, condition) in conditions.iter().enumerate() {
let condition_index = condition_index as u16;
let location = location.clone().with_condition(
condition_index,
condition.description.to_string(),
condition.message.to_string(),
);
let instruction = instructions
.get(&condition.command_type)
.context(error::InstructionNotFound {
name: condition.command_type.to_string(),
location: location.clone(),
})?;
let command = compile_command(
condition,
parameters,
instruction,
&location,
)?;
commands.extend(command);
}
Ok(commands)
}