use std::fmt;
use sim_kernel::{Expr, Symbol};
use sim_lib_stream_core::{DataPacket, StreamPacket};
use sim_value::{access, build};
pub type DeviceSampleResult<T> = std::result::Result<T, DeviceSampleError>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DeviceSampleError {
message: String,
}
impl DeviceSampleError {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
pub fn message(&self) -> &str {
&self.message
}
}
impl fmt::Display for DeviceSampleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for DeviceSampleError {}
impl From<DeviceSampleError> for sim_kernel::Error {
fn from(error: DeviceSampleError) -> Self {
sim_kernel::Error::Eval(format!("device sample error: {error}"))
}
}
pub trait DeviceSample: Sized + Clone {
fn sample_kind() -> &'static str;
fn seq(&self) -> u64;
fn to_expr(&self) -> Expr;
fn from_expr(expr: &Expr) -> DeviceSampleResult<Self>;
}
pub fn roundtrip_ok<S>(sample: &S) -> bool
where
S: DeviceSample + PartialEq,
{
S::from_expr(&sample.to_expr())
.map(|decoded| &decoded == sample)
.unwrap_or(false)
}
pub fn sample_packet<S: DeviceSample>(sample: &S) -> StreamPacket {
StreamPacket::Data(DataPacket::new(
sample_kind_symbol(S::sample_kind()),
sample.to_expr(),
))
}
pub fn device_sample_record_symbol() -> Symbol {
Symbol::qualified("stream", "device-sample")
}
pub fn sample_kind_symbol(kind: &str) -> Symbol {
Symbol::qualified("stream/device-sample", kind)
}
pub fn device_caps_sample_kind_symbol() -> Symbol {
sample_kind_symbol(DeviceCaps::sample_kind())
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DeviceCaps {
seq: u64,
device: Symbol,
streams: Vec<Symbol>,
inputs: Vec<Symbol>,
outputs: Vec<Symbol>,
}
impl DeviceCaps {
pub fn new(
seq: u64,
device: Symbol,
streams: Vec<Symbol>,
inputs: Vec<Symbol>,
outputs: Vec<Symbol>,
) -> Self {
Self {
seq,
device,
streams,
inputs,
outputs,
}
}
pub fn demo(seq: u64) -> Self {
Self::new(
seq,
Symbol::qualified("device", "modeled-edge"),
vec![
Symbol::qualified("device/stream", "battery"),
Symbol::qualified("device/stream", "motion"),
],
vec![Symbol::qualified("device/input", "button")],
vec![
Symbol::qualified("device/output", "screen"),
Symbol::qualified("device/output", "haptic"),
],
)
}
pub fn device(&self) -> &Symbol {
&self.device
}
pub fn streams(&self) -> &[Symbol] {
&self.streams
}
pub fn inputs(&self) -> &[Symbol] {
&self.inputs
}
pub fn outputs(&self) -> &[Symbol] {
&self.outputs
}
pub fn to_stream_packet(&self) -> StreamPacket {
sample_packet(self)
}
}
impl DeviceSample for DeviceCaps {
fn sample_kind() -> &'static str {
"device-caps"
}
fn seq(&self) -> u64 {
self.seq
}
fn to_expr(&self) -> Expr {
build::map(vec![
("kind", Expr::Symbol(device_sample_record_symbol())),
("sample", Expr::Symbol(device_caps_sample_kind_symbol())),
("seq", build::uint(self.seq)),
("device", Expr::Symbol(self.device.clone())),
("streams", symbols_expr(&self.streams)),
("inputs", symbols_expr(&self.inputs)),
("outputs", symbols_expr(&self.outputs)),
])
}
fn from_expr(expr: &Expr) -> DeviceSampleResult<Self> {
let entries = entries(expr)?;
expect_record_tag(entries)?;
expect_sample_kind(entries, &device_caps_sample_kind_symbol())?;
Ok(Self::new(
seq_field(entries)?,
symbol_field(entries, "device")?.clone(),
symbol_list_field(entries, "streams")?,
symbol_list_field(entries, "inputs")?,
symbol_list_field(entries, "outputs")?,
))
}
}
pub(crate) fn decode_known_sample(expr: &Expr) -> DeviceSampleResult<()> {
let entries = entries(expr)?;
expect_record_tag(entries)?;
let kind = symbol_field(entries, "sample")?;
if kind == &device_caps_sample_kind_symbol() {
DeviceCaps::from_expr(expr)?;
return Ok(());
}
Err(DeviceSampleError::new(format!(
"unknown device sample kind {kind}"
)))
}
pub(crate) fn sample_constructor_args(expr: &Expr) -> DeviceSampleResult<Vec<Expr>> {
decode_known_sample(expr)?;
Ok(vec![expr.clone()])
}
fn symbols_expr(symbols: &[Symbol]) -> Expr {
build::list(symbols.iter().cloned().map(Expr::Symbol).collect())
}
fn entries(expr: &Expr) -> DeviceSampleResult<&[(Expr, Expr)]> {
access::map_entries(expr, "device sample map").map_err(kernel_error)
}
fn expect_record_tag(entries: &[(Expr, Expr)]) -> DeviceSampleResult<()> {
let actual = symbol_field(entries, "kind")?;
let expected = device_sample_record_symbol();
if actual == &expected {
Ok(())
} else {
Err(DeviceSampleError::new(format!(
"device sample kind tag must be {expected}, found {actual}"
)))
}
}
fn expect_sample_kind(entries: &[(Expr, Expr)], expected: &Symbol) -> DeviceSampleResult<()> {
let actual = symbol_field(entries, "sample")?;
if actual == expected {
Ok(())
} else {
Err(DeviceSampleError::new(format!(
"device sample record must be {expected}, found {actual}"
)))
}
}
fn seq_field(entries: &[(Expr, Expr)]) -> DeviceSampleResult<u64> {
let value = field(entries, "seq")?;
let Expr::Number(number) = value else {
return Err(DeviceSampleError::new(format!(
"device sample seq must be a u64 number, found {}",
sim_value::kind::expr_kind(value)
)));
};
if !matches!(number.domain.name.as_ref(), "i64" | "u64") {
return Err(DeviceSampleError::new(format!(
"device sample seq must use an integer domain, found {}",
number.domain
)));
}
number
.canonical
.parse::<u64>()
.map_err(|err| DeviceSampleError::new(format!("invalid device sample seq: {err}")))
}
fn symbol_field<'a>(entries: &'a [(Expr, Expr)], name: &str) -> DeviceSampleResult<&'a Symbol> {
access::entry_required_sym(entries, name, "device sample").map_err(kernel_error)
}
fn field<'a>(entries: &'a [(Expr, Expr)], name: &str) -> DeviceSampleResult<&'a Expr> {
access::entry_required(entries, name, "device sample").map_err(kernel_error)
}
fn symbol_list_field(entries: &[(Expr, Expr)], name: &str) -> DeviceSampleResult<Vec<Symbol>> {
let items =
access::entry_required_list(entries, name, "device sample").map_err(kernel_error)?;
items
.iter()
.map(|item| match item {
Expr::Symbol(symbol) => Ok(symbol.clone()),
other => Err(DeviceSampleError::new(format!(
"device sample {name} entries must be symbols, found {}",
sim_value::kind::expr_kind(other)
))),
})
.collect()
}
fn kernel_error(error: sim_kernel::Error) -> DeviceSampleError {
DeviceSampleError::new(error.to_string())
}