kittycad_execution_plan_traits/
lib.rs

1//! Traits and types used for KittyCAD Execution Plans.
2
3pub use self::address::Address;
4use self::events::EventWriter;
5pub use self::primitive::{ListHeader, NumericPrimitive, ObjectHeader, Primitive};
6
7use serde::{Deserialize, Serialize};
8
9mod address;
10mod containers;
11pub mod events;
12#[macro_use]
13mod primitive;
14
15/// Types that can be written to or read from KCEP program memory, in one contiguous block.
16/// If they require multiple memory addresses, they will be laid out
17/// into multiple consecutive memory addresses.
18pub trait Value: Sized {
19    /// Store the value in memory.
20    fn into_parts(self) -> Vec<Primitive>;
21    /// Read the value from memory.
22    fn from_parts<I>(values: &mut I) -> Result<(Self, usize), MemoryError>
23    where
24        I: Iterator<Item = Option<Primitive>>;
25}
26
27/// Types that can be read from KCEP program memory,
28/// scattered across multiple places in the address space.
29pub trait FromMemory: Sized {
30    /// Read this type from memory, getting each field of the type from a different memory address.
31    fn from_memory<I, M>(fields: &mut I, mem: &mut M, events: &mut EventWriter) -> Result<Self, MemoryError>
32    where
33        M: ReadMemory,
34        I: Iterator<Item = InMemory>;
35}
36
37/// Where in memory a value is.
38#[derive(Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, Debug)]
39pub enum InMemory {
40    /// At the given address.
41    Address(Address),
42    /// Top of stack. Pop the value after it's read.
43    StackPop,
44    /// Top of stack. Leave the value there after it's read.
45    StackPeek,
46}
47
48impl From<Address> for InMemory {
49    fn from(a: Address) -> Self {
50        Self::Address(a)
51    }
52}
53
54/// Memory that a KittyCAD Execution Plan can read from.
55pub trait ReadMemory {
56    /// Get a value from the given address.
57    fn get(&self, addr: &Address) -> Option<&Primitive>;
58    /// Same as get but match the return signature of stack operations.
59    fn get_ok(&self, address: &Address) -> Result<Vec<Primitive>, MemoryError> {
60        match self.get(address) {
61            Some(prim) => Ok(vec![prim.clone()]),
62            None => Err(MemoryError::MemoryBadAccess),
63        }
64    }
65
66    /// Get a value from the given starting address. Value might require multiple addresses.
67    fn get_composite<T: Value>(&self, start: Address) -> Result<(T, usize), MemoryError>;
68    /// Remove the value on top of the stack, return it.
69    fn stack_pop(&mut self) -> Result<Vec<Primitive>, MemoryError>;
70    /// Return the value on top of the stack.
71    fn stack_peek(&self) -> Result<Vec<Primitive>, MemoryError>;
72}
73
74/// Errors that could occur when reading a type from KittyCAD Execution Plan program memory.
75#[derive(Debug, thiserror::Error)]
76pub enum MemoryError {
77    /// Something went wrong
78    #[error("Memory was wrong size")]
79    MemoryWrongSize,
80    /// Something went very wrong
81    #[error("Bad memory access")]
82    MemoryBadAccess,
83    /// Type error, memory contained the wrong type.
84    #[error("Tried to read a '{expected}' from KCEP program memory, found an '{actual}' instead")]
85    MemoryWrongType {
86        /// What the KittyCAD executor expected memory to contain
87        expected: &'static str,
88        /// What was actually in memory
89        actual: String,
90    },
91    /// When trying to read an enum from memory, found a variant tag which is not valid for this enum.
92    #[error("Found an unexpected tag '{actual}' when trying to read an enum of type {expected_type} from memory. Looking for one of {}", csv(.valid_variants))]
93    InvalidEnumVariant {
94        /// What type of enum was being read from memory.
95        expected_type: String,
96        /// The actual enum tag found in memory.
97        actual: String,
98        /// Which values would be acceptable?
99        valid_variants: Vec<&'static str>,
100    },
101    /// Stack is empty
102    #[error("Stack is empty")]
103    StackEmpty,
104    /// Stack should have contained a single primitive but it had a composite value instead.
105    #[error("Expected stack to contain a single primitive, but it had a slice of length {actual_length}")]
106    StackNotPrimitive {
107        /// The actual size of the data that was popped off the stack
108        /// Expected to be 1, but it was something else.
109        actual_length: usize,
110    },
111    /// You didn't supply enough fields -- you have to supply one field per field of the original struct
112    #[error("You didn't supply enough fields -- you have to supply one field per field of the original struct")]
113    NotEnoughFields,
114}
115
116fn csv(v: &[&'static str]) -> String {
117    v.join(", ")
118}
119
120/// Macro to generate an `impl Value` for the given type `$subject`.
121/// The type `$subject` must be "primitive-ish",
122/// i.e. something that can be converted Into a Primitive and TryFrom a primitive
123#[macro_export]
124macro_rules! impl_value_on_primitive_ish {
125    ($trait:ident, $subject:ident) => {
126        impl $trait for $subject {
127            fn into_parts(self) -> Vec<Primitive> {
128                vec![self.into()]
129            }
130
131            fn from_parts<I>(values: &mut I) -> Result<(Self, usize), MemoryError>
132            where
133                I: Iterator<Item = Option<Primitive>>,
134            {
135                values
136                    .next()
137                    .ok_or(MemoryError::MemoryWrongSize)?
138                    .to_owned()
139                    .ok_or(MemoryError::MemoryWrongSize)?
140                    .try_into()
141                    .map(|prim| (prim, 1))
142            }
143        }
144    };
145}