Skip to content

Commit

Permalink
execution-plan: Macro for implementing Value on structs (#40)
Browse files Browse the repository at this point in the history
* execution-plan: Macro for implementing Value on a struct

* Impl Value and conversion with primitive for more types
  • Loading branch information
adamchalmers authored Dec 20, 2023
1 parent 3e218fa commit b34a5db
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 31 deletions.
46 changes: 44 additions & 2 deletions execution-plan/src/primitive.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use kittycad_modeling_cmds::shared::Angle;
use kittycad_modeling_cmds::{base64::Base64Data, shared::Angle};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

Expand Down Expand Up @@ -33,6 +33,12 @@ impl From<String> for Primitive {
}
}

impl From<f32> for Primitive {
fn from(value: f32) -> Self {
Self::NumericValue(NumericPrimitive::Float(value as f64))
}
}

impl From<f64> for Primitive {
fn from(value: f64) -> Self {
Self::NumericValue(NumericPrimitive::Float(value))
Expand All @@ -52,6 +58,12 @@ impl From<Vec<u8>> for Primitive {
}
}

impl From<Base64Data> for Primitive {
fn from(value: Base64Data) -> Self {
Self::Bytes(value.into())
}
}

impl TryFrom<Primitive> for String {
type Error = ExecutionError;

Expand Down Expand Up @@ -113,6 +125,14 @@ impl TryFrom<Primitive> for f64 {
}
}

impl TryFrom<Primitive> for f32 {
type Error = ExecutionError;

fn try_from(value: Primitive) -> Result<Self, Self::Error> {
f64::try_from(value).map(|x| x as f32)
}
}

impl TryFrom<Primitive> for Vec<u8> {
type Error = ExecutionError;

Expand All @@ -128,6 +148,14 @@ impl TryFrom<Primitive> for Vec<u8> {
}
}

impl TryFrom<Primitive> for Base64Data {
type Error = ExecutionError;

fn try_from(value: Primitive) -> Result<Self, Self::Error> {
Vec::<u8>::try_from(value).map(Base64Data::from)
}
}

impl TryFrom<Primitive> for bool {
type Error = ExecutionError;

Expand All @@ -143,7 +171,21 @@ impl TryFrom<Primitive> for bool {
}
}

#[cfg(test)]
impl TryFrom<Primitive> for usize {
type Error = ExecutionError;

fn try_from(value: Primitive) -> Result<Self, Self::Error> {
if let Primitive::NumericValue(NumericPrimitive::Integer(x)) = value {
Ok(x)
} else {
Err(ExecutionError::MemoryWrongType {
expected: "usize",
actual: format!("{value:?}"),
})
}
}
}

impl From<usize> for Primitive {
fn from(value: usize) -> Self {
Self::NumericValue(NumericPrimitive::Integer(value))
Expand Down
113 changes: 84 additions & 29 deletions execution-plan/src/value/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
//! - This canonical ordering is the order of the struct's fields in its Rust source code definition.
//! - Enums get laid out by first putting the variant as a string, then putting the variant's fields.
use kittycad_modeling_cmds::{
base64::Base64Data,
ok_response::OkModelingCmdResponse,
output,
shared::{Angle, PathSegment, Point2d, Point3d},
shared::{Angle, PathSegment, Point2d, Point3d, Point4d},
};
use uuid::Uuid;

use super::Value;
use crate::{ExecutionError, Primitive};
Expand All @@ -25,45 +27,98 @@ fn err() -> ExecutionError {
ExecutionError::MemoryWrongSize
}

/// Macro to generate an `impl Value` for the given type `$subject`.
/// The type `$subject` must be "primitive-ish",
/// i.e. something that can be converted Into a Primitive and TryFrom a primitive
macro_rules! impl_value_on_primitive_ish {
($subject:ident) => {
impl Value for $subject {
fn into_parts(self) -> Vec<Primitive> {
vec![self.into()]
}

fn from_parts<I>(values: &mut I) -> Result<Self, ExecutionError>
where
I: Iterator<Item = Option<Primitive>>,
{
values.next().ok_or(err())?.to_owned().ok_or(err())?.try_into()
}
}
};
}

impl_value_on_primitive_ish!(f32);
impl_value_on_primitive_ish!(f64);
impl_value_on_primitive_ish!(bool);
impl_value_on_primitive_ish!(String);
impl_value_on_primitive_ish!(Uuid);
type VecU8 = Vec<u8>;
impl_value_on_primitive_ish!(VecU8);
impl_value_on_primitive_ish!(Angle);
impl_value_on_primitive_ish!(usize);
impl_value_on_primitive_ish!(Base64Data);

/// Macro to generate the methods of trait `Value` for the given fields.
/// Args:
/// `$field`: Repeated 0 or more times. Listing of each field in the struct.
/// The order in which these fields are given determines the order that fields are
/// written to and read from memory.
macro_rules! impl_value_on_struct_fields {
($($field:ident),*) => {
fn into_parts(self) -> Vec<Primitive> {
let mut parts = Vec::new();
$(
parts.extend(self.$field.into_parts());
)*
parts
}

fn from_parts<I>(values: &mut I) -> Result<Self, ExecutionError>
where
I: Iterator<Item = Option<Primitive>>,
{
$(
let $field = Value::from_parts(values)?;
)*
Ok(Self {
$(
$field,
)*
})
}
};
}

impl<T> Value for Point2d<T>
where
Primitive: From<T>,
T: TryFrom<Primitive, Error = ExecutionError>,
T: Value,
{
fn into_parts(self) -> Vec<Primitive> {
let points = [self.x, self.y];
points.into_iter().map(|component| component.into()).collect()
}

fn from_parts<I>(values: &mut I) -> Result<Self, ExecutionError>
where
I: Iterator<Item = Option<Primitive>>,
{
let x = values.next().ok_or(err())?.to_owned().ok_or(err())?.try_into()?;
let y = values.next().ok_or(err())?.to_owned().ok_or(err())?.try_into()?;
Ok(Self { x, y })
}
impl_value_on_struct_fields!(x, y);
}

impl<T> Value for Point3d<T>
where
Primitive: From<T>,
T: TryFrom<Primitive, Error = ExecutionError>,
T: Value,
{
fn into_parts(self) -> Vec<Primitive> {
let points = [self.x, self.y, self.z];
points.into_iter().map(|component| component.into()).collect()
}
impl_value_on_struct_fields!(x, y, z);
}

fn from_parts<I>(values: &mut I) -> Result<Self, ExecutionError>
where
I: Iterator<Item = Option<Primitive>>,
{
let x = values.next().ok_or(err())?.to_owned().ok_or(err())?.try_into()?;
let y = values.next().ok_or(err())?.to_owned().ok_or(err())?.try_into()?;
let z = values.next().ok_or(err())?.to_owned().ok_or(err())?.try_into()?;
Ok(Self { x, y, z })
}
impl<T> Value for Point4d<T>
where
Primitive: From<T>,
T: Value,
{
impl_value_on_struct_fields!(x, y, z, w);
}

impl Value for kittycad_modeling_cmds::shared::Color {
impl_value_on_struct_fields!(r, g, b, a);
}

impl Value for kittycad_modeling_cmds::shared::ExportFile {
impl_value_on_struct_fields!(name, contents);
}

/// Layout:
Expand Down

0 comments on commit b34a5db

Please sign in to comment.