diff --git a/Cargo.lock b/Cargo.lock index 6bc771b8..39a880ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,7 +144,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.41", ] [[package]] @@ -577,7 +577,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.41", ] [[package]] @@ -606,7 +606,7 @@ checksum = "587663dd5fb3d10932c8aecfe7c844db1bcf0aee93eeab08fac13dc1212c2e7f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.41", ] [[package]] @@ -680,7 +680,7 @@ dependencies = [ "diesel_table_macro_syntax", "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.41", ] [[package]] @@ -689,7 +689,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" dependencies = [ - "syn 2.0.40", + "syn 2.0.41", ] [[package]] @@ -718,7 +718,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.41", ] [[package]] @@ -800,7 +800,7 @@ checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.41", ] [[package]] @@ -1010,7 +1010,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.41", ] [[package]] @@ -1519,6 +1519,7 @@ dependencies = [ "image", "insta", "kittycad", + "kittycad-execution-plan-traits", "kittycad-modeling-cmds", "kittycad-modeling-session", "parse-display-derive", @@ -1530,6 +1531,15 @@ dependencies = [ "uuid", ] +[[package]] +name = "kittycad-execution-plan-traits" +version = "0.1.0" +dependencies = [ + "serde", + "thiserror", + "uuid", +] + [[package]] name = "kittycad-modeling-cmds" version = "0.1.8" @@ -1544,6 +1554,7 @@ dependencies = [ "enum-iterator-derive", "euler", "http 0.2.11", + "kittycad-execution-plan-traits", "kittycad-unit-conversion-derive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "measurements", "parse-display", @@ -1938,7 +1949,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.41", ] [[package]] @@ -2090,7 +2101,7 @@ dependencies = [ "regex", "regex-syntax 0.7.5", "structmeta", - "syn 2.0.40", + "syn 2.0.41", ] [[package]] @@ -2156,7 +2167,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.41", ] [[package]] @@ -2901,7 +2912,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.41", ] [[package]] @@ -3096,7 +3107,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta-derive", - "syn 2.0.40", + "syn 2.0.41", ] [[package]] @@ -3107,7 +3118,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.41", ] [[package]] @@ -3179,9 +3190,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.40" +version = "2.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13fa70a4ee923979ffb522cacce59d34421ebdea5625e1073c4326ef9d2dd42e" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" dependencies = [ "proc-macro2", "quote", @@ -3269,22 +3280,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.41", ] [[package]] @@ -3379,7 +3390,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.41", ] [[package]] @@ -3453,7 +3464,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.41", ] [[package]] @@ -3736,7 +3747,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.41", "wasm-bindgen-shared", ] @@ -3770,7 +3781,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.41", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4332,7 +4343,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.40", + "syn 2.0.41", ] [[package]] diff --git a/execution-plan-macros/Cargo.toml b/execution-plan-macros/Cargo.toml new file mode 100644 index 00000000..e63a7b39 --- /dev/null +++ b/execution-plan-macros/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "kittycad-execution-plan-macros" +version = "0.1.0" +edition = "2021" +repository = "https://github.com/KittyCAD/execution-plan" +rust-version = "1.73" +description = "A DSL for composing KittyCAD API queries" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +proc-macro2 = "1.0.70" +quote = "1.0.33" +syn = "2.0.41" + +[lints] +workspace = true + +[lib] +proc-macro = true diff --git a/execution-plan-macros/src/lib.rs b/execution-plan-macros/src/lib.rs new file mode 100644 index 00000000..dfa01312 --- /dev/null +++ b/execution-plan-macros/src/lib.rs @@ -0,0 +1,9 @@ +//! Proc-macros for implementing execution-plan traits. + +use proc_macro::TokenStream; + +/// Test. +#[proc_macro] +pub fn impl_value_trait(_input: TokenStream) -> TokenStream { + todo!() +} diff --git a/execution-plan-traits/Cargo.toml b/execution-plan-traits/Cargo.toml new file mode 100644 index 00000000..d9085639 --- /dev/null +++ b/execution-plan-traits/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "kittycad-execution-plan-traits" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = { version = "1.0.193", features = ["derive"] } +thiserror = "1.0.51" +uuid = "1.6.1" + +[lints] +workspace = true diff --git a/execution-plan-traits/src/lib.rs b/execution-plan-traits/src/lib.rs new file mode 100644 index 00000000..c3b61211 --- /dev/null +++ b/execution-plan-traits/src/lib.rs @@ -0,0 +1,70 @@ +//! TODO +// pub use impl_value_on_primitive_ish; + +pub use self::primitive::{NumericPrimitive, Primitive}; + +#[macro_use] +mod primitive; + +/// Types that can be written to or read from KCEP program memory. +/// If they require multiple memory addresses, they will be laid out +/// into multiple consecutive memory addresses. +pub trait Value: Sized { + /// Store the value in memory. + fn into_parts(self) -> Vec; + /// Read the value from memory. + fn from_parts(values: &mut I) -> Result + where + I: Iterator>; +} + +/// TODO +#[derive(Debug, thiserror::Error, Default)] +pub enum MemoryError { + /// Something went wrong + #[error("Something went wrong")] + #[default] + MemoryWrongSize, + /// Type error, memory contained the wrong type. + #[error("Tried to read a '{expected}' from KCEP program memory, found an '{actual}' instead")] + MemoryWrongType { + /// What the KittyCAD executor expected memory to contain + expected: &'static str, + /// What was actually in memory + actual: String, + }, + /// When trying to read an enum from memory, found a variant tag which is not valid for this enum. + #[error("Found an unexpected tag '{actual}' when trying to read an enum of type {expected_type} from memory")] + InvalidEnumVariant { + /// What type of enum was being read from memory. + expected_type: String, + /// The actual enum tag found in memory. + actual: String, + }, +} + +/// 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_export] +macro_rules! impl_value_on_primitive_ish { + ($trait:ident, $subject:ident) => { + impl $trait for $subject { + fn into_parts(self) -> Vec { + vec![self.into()] + } + + fn from_parts(values: &mut I) -> Result + where + I: Iterator>, + { + values + .next() + .ok_or(MemoryError::MemoryWrongSize)? + .to_owned() + .ok_or(MemoryError::MemoryWrongSize)? + .try_into() + } + } + }; +} diff --git a/execution-plan/src/primitive.rs b/execution-plan-traits/src/primitive.rs similarity index 67% rename from execution-plan/src/primitive.rs rename to execution-plan-traits/src/primitive.rs index fcd7ebf5..f726bb58 100644 --- a/execution-plan/src/primitive.rs +++ b/execution-plan-traits/src/primitive.rs @@ -1,17 +1,22 @@ -use kittycad_modeling_cmds::{base64::Base64Data, shared::Angle}; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::ExecutionError; +use crate::{impl_value_on_primitive_ish, MemoryError, Value}; /// A value stored in KCEP program memory. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub enum Primitive { + /// UTF-8 text String(String), + /// Various number kinds NumericValue(NumericPrimitive), + /// UUID Uuid(Uuid), + /// Raw binary Bytes(Vec), + /// True or false Bool(bool), + /// An optional value which was not given. Nil, } @@ -45,33 +50,20 @@ impl From for Primitive { } } -/// Angle is always stored as f64 degrees. -impl From for Primitive { - fn from(value: Angle) -> Self { - Self::NumericValue(NumericPrimitive::Float(value.to_degrees())) - } -} - impl From> for Primitive { fn from(value: Vec) -> Self { Self::Bytes(value) } } -impl From for Primitive { - fn from(value: Base64Data) -> Self { - Self::Bytes(value.into()) - } -} - impl TryFrom for String { - type Error = ExecutionError; + type Error = MemoryError; fn try_from(value: Primitive) -> Result { if let Primitive::String(s) = value { Ok(s) } else { - Err(ExecutionError::MemoryWrongType { + Err(MemoryError::MemoryWrongType { expected: "string", actual: format!("{value:?}"), }) @@ -80,13 +72,13 @@ impl TryFrom for String { } impl TryFrom for Uuid { - type Error = ExecutionError; + type Error = MemoryError; fn try_from(value: Primitive) -> Result { if let Primitive::Uuid(u) = value { Ok(u) } else { - Err(ExecutionError::MemoryWrongType { + Err(MemoryError::MemoryWrongType { expected: "uuid", actual: format!("{value:?}"), }) @@ -94,30 +86,14 @@ impl TryFrom for Uuid { } } -/// Angle is always stored as f64 degrees. -impl TryFrom for Angle { - type Error = ExecutionError; - - fn try_from(value: Primitive) -> Result { - if let Primitive::NumericValue(x) = value { - Ok(Angle::from_degrees(x.into())) - } else { - Err(ExecutionError::MemoryWrongType { - expected: "number", - actual: format!("{value:?}"), - }) - } - } -} - impl TryFrom for f64 { - type Error = ExecutionError; + type Error = MemoryError; fn try_from(value: Primitive) -> Result { if let Primitive::NumericValue(NumericPrimitive::Float(x)) = value { Ok(x) } else { - Err(ExecutionError::MemoryWrongType { + Err(MemoryError::MemoryWrongType { expected: "float", actual: format!("{value:?}"), }) @@ -126,7 +102,7 @@ impl TryFrom for f64 { } impl TryFrom for f32 { - type Error = ExecutionError; + type Error = MemoryError; fn try_from(value: Primitive) -> Result { f64::try_from(value).map(|x| x as f32) @@ -134,13 +110,13 @@ impl TryFrom for f32 { } impl TryFrom for Vec { - type Error = ExecutionError; + type Error = MemoryError; fn try_from(value: Primitive) -> Result { if let Primitive::Bytes(x) = value { Ok(x) } else { - Err(ExecutionError::MemoryWrongType { + Err(MemoryError::MemoryWrongType { expected: "bytes", actual: format!("{value:?}"), }) @@ -148,22 +124,14 @@ impl TryFrom for Vec { } } -impl TryFrom for Base64Data { - type Error = ExecutionError; - - fn try_from(value: Primitive) -> Result { - Vec::::try_from(value).map(Base64Data::from) - } -} - impl TryFrom for bool { - type Error = ExecutionError; + type Error = MemoryError; fn try_from(value: Primitive) -> Result { if let Primitive::Bool(x) = value { Ok(x) } else { - Err(ExecutionError::MemoryWrongType { + Err(MemoryError::MemoryWrongType { expected: "bool", actual: format!("{value:?}"), }) @@ -172,13 +140,13 @@ impl TryFrom for bool { } impl TryFrom for usize { - type Error = ExecutionError; + type Error = MemoryError; fn try_from(value: Primitive) -> Result { if let Primitive::NumericValue(NumericPrimitive::Integer(x)) = value { Ok(x) } else { - Err(ExecutionError::MemoryWrongType { + Err(MemoryError::MemoryWrongType { expected: "usize", actual: format!("{value:?}"), }) @@ -192,25 +160,28 @@ impl From for Primitive { } } +/// Various kinds of number. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub enum NumericPrimitive { + /// Unsigned integer Integer(usize), + /// Floating point Float(f64), } -impl crate::value::Value for Primitive { +impl crate::Value for Primitive { fn into_parts(self) -> Vec { vec![self] } - fn from_parts(values: &mut I) -> Result + fn from_parts(values: &mut I) -> Result where I: Iterator>, { values .next() .and_then(|v| v.to_owned()) - .ok_or(ExecutionError::MemoryWrongSize) + .ok_or(MemoryError::MemoryWrongSize) } } @@ -222,3 +193,12 @@ impl From for f64 { } } } + +impl_value_on_primitive_ish!(Value, f32); +impl_value_on_primitive_ish!(Value, f64); +impl_value_on_primitive_ish!(Value, bool); +impl_value_on_primitive_ish!(Value, String); +impl_value_on_primitive_ish!(Value, Uuid); +type VecU8 = Vec; +impl_value_on_primitive_ish!(Value, VecU8); +impl_value_on_primitive_ish!(Value, usize); diff --git a/execution-plan/Cargo.toml b/execution-plan/Cargo.toml index dfe333a1..6753f42b 100644 --- a/execution-plan/Cargo.toml +++ b/execution-plan/Cargo.toml @@ -11,6 +11,7 @@ description = "A DSL for composing KittyCAD API queries" bytes = "1.5" insta = "1.34.0" kittycad = { version = "0.2.44", features = ["requests"] } +kittycad-execution-plan-traits = { path = "../execution-plan-traits" } kittycad-modeling-cmds = { path = "../modeling-cmds" } kittycad-modeling-session = { path = "../modeling-session" } parse-display-derive = "0.8.2" diff --git a/execution-plan/src/api_endpoint.rs b/execution-plan/src/api_endpoint.rs index 8e6c7d52..3db9cf95 100644 --- a/execution-plan/src/api_endpoint.rs +++ b/execution-plan/src/api_endpoint.rs @@ -1,3 +1,4 @@ +use kittycad_execution_plan_traits::{MemoryError, Primitive, Value}; use kittycad_modeling_cmds::{ each_cmd::{MovePathPen, StartPath}, id::ModelingCmdId, @@ -5,7 +6,7 @@ use kittycad_modeling_cmds::{ }; use uuid::Uuid; -use crate::{primitive::Primitive, value::Value, Address, ExecutionError, Memory, Result}; +use crate::{Address, ExecutionError, Memory, Result}; /// All API endpoints that can be executed must implement this trait. pub trait ApiEndpoint: ModelingCmdVariant + Sized { @@ -47,6 +48,7 @@ impl ApiEndpoint for ExtendPath { { let path = read::(fields.next(), mem) .and_then(Uuid::try_from) + .map_err(ExecutionError::from) .map(ModelingCmdId::from)?; let segment = read(fields.next(), mem)?; Ok(Self { path, segment }) @@ -73,9 +75,11 @@ impl ApiEndpoint for TakeSnapshot { I: Iterator, { let format_str = read::(fields.next(), mem).and_then(String::try_from)?; - let format = format_str.parse().map_err(|_| ExecutionError::InvalidEnumVariant { - expected_type: "image format".to_owned(), - actual: format_str, + let format = format_str.parse().map_err(|_| { + ExecutionError::from(MemoryError::InvalidEnumVariant { + expected_type: "image format".to_owned(), + actual: format_str, + }) })?; Ok(Self { format }) } @@ -91,7 +95,7 @@ impl ApiEndpoint for ClosePath { } } -fn read(start_addr: Option
, mem: &Memory) -> Result { - let start_addr = start_addr.ok_or(ExecutionError::MemoryWrongSize)?; +fn read(start_addr: Option
, mem: &Memory) -> std::result::Result { + let start_addr = start_addr.ok_or(MemoryError::MemoryWrongSize)?; mem.get_composite(start_addr) } diff --git a/execution-plan/src/arithmetic.rs b/execution-plan/src/arithmetic.rs index 2f2f26da..195e0483 100644 --- a/execution-plan/src/arithmetic.rs +++ b/execution-plan/src/arithmetic.rs @@ -1,7 +1,8 @@ -use crate::primitive::{NumericPrimitive, Primitive}; -use crate::{ExecutionError, Memory, Operand, Operation}; +use kittycad_execution_plan_traits::{NumericPrimitive, Primitive}; use serde::{Deserialize, Serialize}; +use crate::{ExecutionError, Memory, Operand, Operation}; + /// Instruction to perform arithmetic on values in memory. #[derive(Deserialize, Serialize)] pub struct Arithmetic { @@ -31,9 +32,7 @@ macro_rules! arithmetic_body { (NumericPrimitive::Float(x), NumericPrimitive::Integer(y)) => { NumericPrimitive::Float(x.$method(y as f64)) } - (NumericPrimitive::Float(x), NumericPrimitive::Float(y)) => { - NumericPrimitive::Float(x.$method(y)) - } + (NumericPrimitive::Float(x), NumericPrimitive::Float(y)) => NumericPrimitive::Float(x.$method(y)), }; Ok(Primitive::NumericValue(num)) } diff --git a/execution-plan/src/lib.rs b/execution-plan/src/lib.rs index 168d4395..0e7d9bff 100644 --- a/execution-plan/src/lib.rs +++ b/execution-plan/src/lib.rs @@ -9,20 +9,19 @@ use std::fmt; use api_endpoint::ApiEndpoint; +use kittycad_execution_plan_traits::{MemoryError, Primitive}; use kittycad_modeling_cmds::{each_cmd, id::ModelingCmdId}; use kittycad_modeling_session::{RunCommandError, Session as ModelingSession}; pub use memory::{Memory, StaticMemoryInitializer}; use serde::{Deserialize, Serialize}; -use self::{arithmetic::Arithmetic, primitive::Primitive}; +use self::arithmetic::Arithmetic; mod api_endpoint; mod arithmetic; mod memory; -mod primitive; #[cfg(test)] mod tests; -mod value; /// An address in KCEP's program memory. #[derive(Debug, Clone, Copy, Serialize, Deserialize)] @@ -231,17 +230,6 @@ pub enum ExecutionError { /// Operands being attempted operands: Vec, }, - /// Type error, memory contained the wrong type. - #[error("Tried to read a '{expected}' from KCEP program memory, found an '{actual}' instead")] - MemoryWrongType { - /// What the KittyCAD executor expected memory to contain - expected: &'static str, - /// What was actually in memory - actual: String, - }, - /// Memory address was not set. - #[error("Tried to read from empty memory address")] - MemoryWrongSize, /// You tried to call a KittyCAD endpoint that doesn't exist or isn't implemented. #[error("No endpoint {name} recognized")] UnrecognizedEndpoint { @@ -251,12 +239,7 @@ pub enum ExecutionError { /// Error running a modeling command. #[error("Error sending command to API: {0}")] ModelingApiError(#[from] RunCommandError), - /// When trying to read an enum from memory, found a variant tag which is not valid for this enum. - #[error("Found an unexpected tag '{actual}' when trying to read an enum of type {expected_type} from memory")] - InvalidEnumVariant { - /// What type of enum was being read from memory. - expected_type: String, - /// The actual enum tag found in memory. - actual: String, - }, + /// Error reading value from memory. + #[error("{0}")] + MemoryError(#[from] MemoryError), } diff --git a/execution-plan/src/memory.rs b/execution-plan/src/memory.rs index aa8c826b..ac76a31a 100644 --- a/execution-plan/src/memory.rs +++ b/execution-plan/src/memory.rs @@ -1,5 +1,6 @@ -use super::Result; -use crate::{primitive::Primitive, value::Value, Address}; +use kittycad_execution_plan_traits::{MemoryError, Primitive, Value}; + +use crate::Address; /// Helper wrapper around Memory. It lets you push static data into memory before the program runs. pub struct StaticMemoryInitializer { @@ -78,7 +79,7 @@ impl Memory { /// Get a value value (i.e. a value which takes up multiple addresses in memory). /// Its parts are stored in consecutive memory addresses starting at `start`. - pub fn get_composite(&self, start: Address) -> Result { + pub fn get_composite(&self, start: Address) -> std::result::Result { let mut values = self.addresses.iter().skip(start.0).cloned(); T::from_parts(&mut values) } diff --git a/execution-plan/src/tests.rs b/execution-plan/src/tests.rs index b36c0aa7..862d9444 100644 --- a/execution-plan/src/tests.rs +++ b/execution-plan/src/tests.rs @@ -1,13 +1,13 @@ use std::env; use insta::assert_snapshot; +use kittycad_execution_plan_traits::{NumericPrimitive, Primitive}; use kittycad_modeling_cmds::shared::{PathSegment, Point3d}; use kittycad_modeling_session::{Session, SessionBuilder}; use tabled::{settings::Style, Table}; use uuid::Uuid; use super::*; -use crate::primitive::NumericPrimitive; async fn test_client() -> Session { let kittycad_api_token = env::var("KITTYCAD_API_TOKEN").expect("You must set $KITTYCAD_API_TOKEN"); @@ -257,17 +257,15 @@ async fn api_call_draw_cube() { /// Return a nicely-formatted table of memory. fn debug_dump_memory(mem: &Memory) -> String { - impl Primitive { - fn pretty_print(&self) -> (&'static str, String) { - match self { - Primitive::String(v) => ("String", v.to_owned()), - Primitive::NumericValue(NumericPrimitive::Float(v)) => ("Float", v.to_string()), - Primitive::NumericValue(NumericPrimitive::Integer(v)) => ("Integer", v.to_string()), - Primitive::Uuid(v) => ("Uuid", v.to_string()), - Primitive::Bytes(v) => ("Bytes", format!("length {}", v.len())), - Primitive::Bool(v) => ("Bool", v.to_string()), - Primitive::Nil => ("Nil", String::new()), - } + fn pretty_print(p: &Primitive) -> (&'static str, String) { + match p { + Primitive::String(v) => ("String", v.to_owned()), + Primitive::NumericValue(NumericPrimitive::Float(v)) => ("Float", v.to_string()), + Primitive::NumericValue(NumericPrimitive::Integer(v)) => ("Integer", v.to_string()), + Primitive::Uuid(v) => ("Uuid", v.to_string()), + Primitive::Bytes(v) => ("Bytes", format!("length {}", v.len())), + Primitive::Bool(v) => ("Bool", v.to_string()), + Primitive::Nil => ("Nil", String::new()), } } #[derive(tabled::Tabled)] @@ -280,7 +278,7 @@ fn debug_dump_memory(mem: &Memory) -> String { mem.iter() .filter_map(|(i, val)| { if let Some(val) = val { - let (val_type, value) = val.pretty_print(); + let (val_type, value) = pretty_print(val); Some(MemoryAddr { index: i, val_type, diff --git a/execution-plan/src/value.rs b/execution-plan/src/value.rs deleted file mode 100644 index a793d5dc..00000000 --- a/execution-plan/src/value.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::{ExecutionError, Primitive}; - -pub mod impls; - -/// Types that can be written to or read from KCEP program memory. -/// If they require multiple memory addresses, they will be laid out -/// into multiple consecutive memory addresses. -pub trait Value: Sized { - /// Store the value in memory. - fn into_parts(self) -> Vec; - /// Read the value from memory. - fn from_parts(values: &mut I) -> Result - where - I: Iterator>; -} diff --git a/modeling-cmds/Cargo.toml b/modeling-cmds/Cargo.toml index d01c7bdf..4f1af8c8 100644 --- a/modeling-cmds/Cargo.toml +++ b/modeling-cmds/Cargo.toml @@ -21,6 +21,7 @@ enum-iterator = "1.4.1" enum-iterator-derive = "1.2.1" euler = "0.4.1" http = "0.2.9" +kittycad-execution-plan-traits = { path = "../execution-plan-traits" } kittycad-unit-conversion-derive = "0.1.0" measurements = "0.11.0" parse-display = "0.8.2" diff --git a/modeling-cmds/src/kcep_primitive.rs b/modeling-cmds/src/kcep_primitive.rs new file mode 100644 index 00000000..03af498c --- /dev/null +++ b/modeling-cmds/src/kcep_primitive.rs @@ -0,0 +1,42 @@ +use kittycad_execution_plan_traits::{impl_value_on_primitive_ish, MemoryError, NumericPrimitive, Primitive, Value}; + +use crate::{base64::Base64Data, shared::Angle}; + +/// Angle is always stored as f64 degrees. +impl From for Primitive { + fn from(value: Angle) -> Self { + Self::NumericValue(NumericPrimitive::Float(value.to_degrees())) + } +} + +impl From for Primitive { + fn from(value: Base64Data) -> Self { + Self::Bytes(value.into()) + } +} +/// Angle is always stored as f64 degrees. +impl TryFrom for Angle { + type Error = MemoryError; + + fn try_from(value: Primitive) -> Result { + if let Primitive::NumericValue(x) = value { + Ok(Angle::from_degrees(x.into())) + } else { + Err(MemoryError::MemoryWrongType { + expected: "number", + actual: format!("{value:?}"), + }) + } + } +} + +impl TryFrom for Base64Data { + type Error = MemoryError; + + fn try_from(value: Primitive) -> Result { + Vec::::try_from(value).map(Base64Data::from) + } +} + +impl_value_on_primitive_ish!(Value, Angle); +impl_value_on_primitive_ish!(Value, Base64Data); diff --git a/execution-plan/src/value/impls.rs b/modeling-cmds/src/kcep_value.rs similarity index 78% rename from execution-plan/src/value/impls.rs rename to modeling-cmds/src/kcep_value.rs index 00afe7aa..859ab8ec 100644 --- a/execution-plan/src/value/impls.rs +++ b/modeling-cmds/src/kcep_value.rs @@ -1,62 +1,10 @@ -//! General principles for implementing Value -//! -//! - Each struct should have a canonical ordering for its fields. -//! - Always lay these fields out in the canonical ordering. -//! - 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, +use kittycad_execution_plan_traits::{MemoryError, Primitive, Value}; + +use crate::{ ok_response::OkModelingCmdResponse, output, - shared::{Angle, PathSegment, Point2d, Point3d, Point4d}, + shared::{Angle, Color, ExportFile, PathSegment, Point2d, Point3d, Point4d}, }; -use uuid::Uuid; - -use super::Value; -use crate::{ExecutionError, Primitive}; - -pub(crate) const EMPTY: &str = "EMPTY"; -pub(crate) const TAKE_SNAPSHOT: &str = "TAKE_SNAPSHOT"; -pub(crate) const ARC: &str = "arc"; -pub(crate) const LINE: &str = "line"; -pub(crate) const TAN_ARC: &str = "tan_arc"; -pub(crate) const TAN_ARC_TO: &str = "tan_arc_to"; -pub(crate) const BEZIER: &str = "bezier"; - -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 { - vec![self.into()] - } - - fn from_parts(values: &mut I) -> Result - where - I: Iterator>, - { - 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; -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: @@ -73,7 +21,7 @@ macro_rules! impl_value_on_struct_fields { parts } - fn from_parts(values: &mut I) -> Result + fn from_parts(values: &mut I) -> Result where I: Iterator>, { @@ -113,14 +61,26 @@ where impl_value_on_struct_fields!(x, y, z, w); } -impl Value for kittycad_modeling_cmds::shared::Color { +impl Value for Color { impl_value_on_struct_fields!(r, g, b, a); } -impl Value for kittycad_modeling_cmds::shared::ExportFile { +impl Value for ExportFile { impl_value_on_struct_fields!(name, contents); } +pub(crate) const EMPTY: &str = "EMPTY"; +pub(crate) const TAKE_SNAPSHOT: &str = "TAKE_SNAPSHOT"; +pub(crate) const ARC: &str = "arc"; +pub(crate) const LINE: &str = "line"; +pub(crate) const TAN_ARC: &str = "tan_arc"; +pub(crate) const TAN_ARC_TO: &str = "tan_arc_to"; +pub(crate) const BEZIER: &str = "bezier"; + +fn err() -> MemoryError { + MemoryError::MemoryWrongSize +} + /// Layout: /// - One memory address to store the variant name /// - Following memory addresses to store the variant's single field. @@ -137,7 +97,7 @@ impl Value for OkModelingCmdResponse { } } - fn from_parts(values: &mut I) -> Result + fn from_parts(values: &mut I) -> Result where I: Iterator>, { @@ -161,7 +121,7 @@ impl Value for output::TakeSnapshot { vec![Primitive::Bytes(self.contents.into())] } - fn from_parts(values: &mut I) -> Result + fn from_parts(values: &mut I) -> Result where I: Iterator>, { @@ -174,10 +134,10 @@ impl Value for output::TakeSnapshot { /// Read the next primitive. /// If it's -fn next(values: &mut I) -> Result +fn next(values: &mut I) -> Result where I: Iterator>, - T: TryFrom, + T: TryFrom, { let v = values.next().ok_or_else(err)?; let v = v.ok_or_else(err)?; @@ -250,7 +210,7 @@ impl Value for PathSegment { parts } - fn from_parts(values: &mut I) -> Result + fn from_parts(values: &mut I) -> Result where I: Iterator>, { @@ -304,7 +264,7 @@ impl Value for PathSegment { angle_snap_increment, }) } - other => Err(ExecutionError::InvalidEnumVariant { + other => Err(MemoryError::InvalidEnumVariant { expected_type: "line segment".to_owned(), actual: other.to_owned(), }), diff --git a/modeling-cmds/src/lib.rs b/modeling-cmds/src/lib.rs index cc3b6d59..b4f83c10 100644 --- a/modeling-cmds/src/lib.rs +++ b/modeling-cmds/src/lib.rs @@ -15,6 +15,8 @@ pub mod id; #[cfg(feature = "cxx")] pub mod impl_extern_type; mod impl_traits; +mod kcep_primitive; +mod kcep_value; /// When a modeling command is successful, these responses could be returned. pub mod ok_response; /// Output of each modeling command.