diff --git a/src/wasm-lib/Cargo.lock b/src/wasm-lib/Cargo.lock index bc1821ee5..734fcf72e 100644 --- a/src/wasm-lib/Cargo.lock +++ b/src/wasm-lib/Cargo.lock @@ -2590,6 +2590,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-kcl" +version = "0.1.0" +dependencies = [ + "ryu", + "serde", + "thiserror", +] + [[package]] name = "serde_bytes" version = "0.11.14" diff --git a/src/wasm-lib/Cargo.toml b/src/wasm-lib/Cargo.toml index dd0015ccf..35d3c2890 100644 --- a/src/wasm-lib/Cargo.toml +++ b/src/wasm-lib/Cargo.toml @@ -67,6 +67,7 @@ members = [ "kcl", "kcl-macros", "kcl-test-server", + "serde-kcl", ] [workspace.dependencies] diff --git a/src/wasm-lib/serde-kcl/Cargo.toml b/src/wasm-lib/serde-kcl/Cargo.toml new file mode 100644 index 000000000..14e0ec9e7 --- /dev/null +++ b/src/wasm-lib/serde-kcl/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "serde-kcl" +description = "KittyCAD Language object model" +version = "0.1.0" +edition = "2021" +repository = "https://github.com/KittyCAD/modeling-app" +rust-version = "1.80" +license = "MIT" +authors = ["Jess Frazelle", "Adam Chalmers", "Jon Tran", "KittyCAD, Inc"] +keywords = ["kcl", "KittyCAD", "CAD"] + +[dependencies] +ryu = "1.0" +serde = "1.0.207" +thiserror = "1.0.63" + +[dev-dependencies] +serde = { version = "1.0.207", features = ["derive"]} diff --git a/src/wasm-lib/serde-kcl/src/error.rs b/src/wasm-lib/serde-kcl/src/error.rs new file mode 100644 index 000000000..b3b048bc4 --- /dev/null +++ b/src/wasm-lib/serde-kcl/src/error.rs @@ -0,0 +1,29 @@ +use std::{fmt::Display, num::TryFromIntError}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{0}")] + Message(String), + #[error("Number is too big")] + NumberTooBig, + #[error("You cannot use this as a key of a KCL object")] + InvalidKey, +} + +impl From for Error { + fn from(_: TryFromIntError) -> Self { + Self::NumberTooBig + } +} + +impl serde::ser::Error for Error { + fn custom(msg: T) -> Self { + Self::Message(msg.to_string()) + } +} + +impl serde::de::Error for Error { + fn custom(msg: T) -> Self { + Self::Message(msg.to_string()) + } +} diff --git a/src/wasm-lib/serde-kcl/src/lib.rs b/src/wasm-lib/serde-kcl/src/lib.rs new file mode 100644 index 000000000..275541964 --- /dev/null +++ b/src/wasm-lib/serde-kcl/src/lib.rs @@ -0,0 +1,44 @@ +use serde::Serialize; + +pub use crate::error::Error; +pub use crate::object::Object; +pub use crate::value::Value; + +mod error; +mod object; +mod value; + +pub fn to_value(value: T) -> Result +where + T: Serialize, +{ + value.serialize(crate::value::ser::Serializer) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn structs_into_kcl_object() { + #[derive(serde::Serialize)] + struct Person { + name: String, + age: u8, + } + + let adam = Person { + name: "Adam".to_owned(), + age: 32, + }; + let val = to_value(&adam).expect("Serializing to KCL object should pass"); + let obj = val.as_object().unwrap(); + let expected = Object { + properties: std::collections::HashMap::from([ + ("name".to_owned(), Value::from("Adam".to_owned())), + ("age".to_owned(), Value::from(32)), + ]), + }; + assert_eq!(obj.properties, expected.properties); + } +} diff --git a/src/wasm-lib/serde-kcl/src/object.rs b/src/wasm-lib/serde-kcl/src/object.rs new file mode 100644 index 000000000..b81b3b638 --- /dev/null +++ b/src/wasm-lib/serde-kcl/src/object.rs @@ -0,0 +1,35 @@ +use std::collections::HashMap; + +use crate::Value; + +/// A KCL object. +#[derive(Debug, Default, PartialEq)] +pub struct Object { + /// The object's properties. + pub properties: HashMap, +} + +impl Object { + /// Create a new object with no properties. + pub fn new() -> Self { + Self::default() + } + /// How many properties does this object have? + pub fn len(&self) -> usize { + self.properties.len() + } + /// Add a new property to the object. + /// If the object already has a property with this name, overwrites it. + pub fn insert(&mut self, property: String, value: Value) { + self.properties.insert(property, value); + } +} + +/// Given a list of (key, value) pairs, you can make a KCL object. +impl From<[(String, Value); N]> for Object { + fn from(value: [(String, Value); N]) -> Self { + Self { + properties: HashMap::from(value), + } + } +} diff --git a/src/wasm-lib/serde-kcl/src/value.rs b/src/wasm-lib/serde-kcl/src/value.rs new file mode 100644 index 000000000..7c04979c1 --- /dev/null +++ b/src/wasm-lib/serde-kcl/src/value.rs @@ -0,0 +1,84 @@ +use crate::Object; + +pub(crate) mod ser; + +#[derive(Debug, PartialEq)] +pub enum Value { + /// A value to use when the specific value isn't really important. + /// For example, this is the return type of functions that don't return + /// any other value. + /// + /// Don't worry about it too much. + /// + /// Kind of like 'null' in other languages, but it doesn't have the + /// connotation that nothing was missing. It probably means nothing was + /// required, not nothing was found. + Unit, + /// Either true or false. + Boolean(bool), + /// Text. + String(String), + /// Whole numbers (positive, negative or zero). + Integer(i64), + /// Numbers with a fractional part. + Float(f64), + /// A list of other values. + Array(Vec), + /// A set of properties. Each property has a name (aka "key") and a value. + Object(Object), + /// Binary data + Bytes(Vec), +} + +macro_rules! impl_as { + ($name:ident, $variant:ident, $return_type:ty) => { + pub fn $name(&self) -> Option<&$return_type> { + match self { + Self::$variant(x) => Some(x), + _ => None, + } + } + }; +} + +macro_rules! impl_from { + ($variant:ident, $t:ty) => { + impl From<$t> for Value { + fn from(t: $t) -> Self { + Self::$variant(t.into()) + } + } + }; +} + +impl Value { + impl_as!(as_boolean, Boolean, bool); + impl_as!(as_string, String, String); + impl_as!(as_integer, Integer, i64); + impl_as!(as_float, Float, f64); + impl_as!(as_array, Array, Vec); + impl_as!(as_object, Object, Object); + impl_as!(as_binary, Bytes, Vec); + pub fn as_unit(&self) -> Option<()> { + match self { + Self::Unit => Some(()), + _ => None, + } + } +} +impl_from!(String, String); +impl_from!(Boolean, bool); +impl_from!(Integer, i64); +impl_from!(Integer, i32); +impl_from!(Integer, u32); +impl_from!(Integer, u8); +impl_from!(Integer, i8); +impl_from!(Float, f64); +impl_from!(Float, f32); +impl_from!(Bytes, Vec); + +impl From<()> for Value { + fn from(_: ()) -> Self { + Self::Unit + } +} diff --git a/src/wasm-lib/serde-kcl/src/value/ser.rs b/src/wasm-lib/serde-kcl/src/value/ser.rs new file mode 100644 index 000000000..2f7c725db --- /dev/null +++ b/src/wasm-lib/serde-kcl/src/value/ser.rs @@ -0,0 +1,591 @@ +use serde::ser::Impossible; +use serde::Serialize; + +use crate::value::Value; +use crate::{to_value, Error, Object}; + +// We only use our own error type; no need for From conversions provided by the +// standard library's try! macro. This reduces lines of LLVM IR by 4%. +macro_rules! tri { + ($e:expr $(,)?) => { + match $e { + core::result::Result::Ok(val) => val, + core::result::Result::Err(err) => return core::result::Result::Err(err), + } + }; +} + +impl Serialize for Value { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: ::serde::Serializer, + { + match self { + Self::Unit => serializer.serialize_unit(), + Self::Boolean(b) => serializer.serialize_bool(*b), + Self::String(s) => serializer.serialize_str(s), + Self::Integer(x) => serializer.serialize_i64(*x), + Self::Float(x) => serializer.serialize_f64(*x), + Self::Bytes(b) => serializer.serialize_bytes(b), + Self::Array(v) => serializer.collect_seq(v), + Self::Object(o) => { + use serde::ser::SerializeMap; + let mut map = serializer.serialize_map(Some(o.len()))?; + for (k, v) in &o.properties { + map.serialize_entry(k, v)?; + } + map.end() + } + } + } +} + +pub(crate) struct Serializer; + +type Result = std::result::Result; + +pub struct SerializeVec { + vec: Vec, +} + +impl serde::ser::SerializeSeq for SerializeVec { + type Ok = Value; + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + self.vec.push(tri!(to_value(value))); + Ok(()) + } + + fn end(self) -> Result { + Ok(Value::Array(self.vec)) + } +} + +impl serde::ser::SerializeTuple for SerializeVec { + type Ok = Value; + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + serde::ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> Result { + serde::ser::SerializeSeq::end(self) + } +} + +impl serde::ser::SerializeTupleStruct for SerializeVec { + type Ok = Value; + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + serde::ser::SerializeSeq::serialize_element(self, value) + } + + fn end(self) -> Result { + serde::ser::SerializeSeq::end(self) + } +} + +struct MapKeySerializer; + +fn key_must_be_a_string() -> Error { + Error::InvalidKey +} + +fn float_key_must_be_finite() -> Error { + Error::InvalidKey +} + +impl serde::Serializer for MapKeySerializer { + type Ok = String; + type Error = Error; + + type SerializeSeq = Impossible; + type SerializeTuple = Impossible; + type SerializeTupleStruct = Impossible; + type SerializeTupleVariant = Impossible; + type SerializeMap = Impossible; + type SerializeStruct = Impossible; + type SerializeStructVariant = Impossible; + + #[inline] + fn serialize_unit_variant(self, _name: &'static str, _variant_index: u32, variant: &'static str) -> Result { + Ok(variant.to_owned()) + } + + #[inline] + fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + fn serialize_bool(self, value: bool) -> Result { + Ok(value.to_string()) + } + + fn serialize_i8(self, value: i8) -> Result { + Ok(value.to_string()) + } + + fn serialize_i16(self, value: i16) -> Result { + Ok(value.to_string()) + } + + fn serialize_i32(self, value: i32) -> Result { + Ok(value.to_string()) + } + + fn serialize_i64(self, value: i64) -> Result { + Ok(value.to_string()) + } + + fn serialize_u8(self, value: u8) -> Result { + Ok(value.to_string()) + } + + fn serialize_u16(self, value: u16) -> Result { + Ok(value.to_string()) + } + + fn serialize_u32(self, value: u32) -> Result { + Ok(value.to_string()) + } + + fn serialize_u64(self, value: u64) -> Result { + Ok(value.to_string()) + } + + fn serialize_f32(self, value: f32) -> Result { + if value.is_finite() { + Ok(ryu::Buffer::new().format_finite(value).to_owned()) + } else { + Err(float_key_must_be_finite()) + } + } + + fn serialize_f64(self, value: f64) -> Result { + if value.is_finite() { + Ok(ryu::Buffer::new().format_finite(value).to_owned()) + } else { + Err(float_key_must_be_finite()) + } + } + + #[inline] + fn serialize_char(self, value: char) -> Result { + Ok({ + let mut s = String::new(); + s.push(value); + s + }) + } + + #[inline] + fn serialize_str(self, value: &str) -> Result { + Ok(value.to_owned()) + } + + fn serialize_bytes(self, _value: &[u8]) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_unit(self) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _value: &T, + ) -> Result + where + T: ?Sized + Serialize, + { + Err(key_must_be_a_string()) + } + + fn serialize_none(self) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_some(self, _value: &T) -> Result + where + T: ?Sized + Serialize, + { + Err(key_must_be_a_string()) + } + + fn serialize_seq(self, _len: Option) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_tuple(self, _len: usize) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_tuple_struct(self, _name: &'static str, _len: usize) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_map(self, _len: Option) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_struct(self, _name: &'static str, _len: usize) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(key_must_be_a_string()) + } + + fn collect_str(self, value: &T) -> Result + where + T: ?Sized + std::fmt::Display, + { + Ok(value.to_string()) + } +} + +pub struct SerializeTupleVariant { + name: String, + vec: Vec, +} + +impl serde::ser::SerializeTupleVariant for SerializeTupleVariant { + type Ok = Value; + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + self.vec.push(to_value(value)?); + Ok(()) + } + + fn end(self) -> Result { + let mut object = Object::new(); + + object.insert(self.name, Value::Array(self.vec)); + + Ok(Value::Object(object)) + } +} + +pub enum SerializeMap { + Map { map: Object, next_key: Option }, +} + +impl serde::ser::SerializeMap for SerializeMap { + type Ok = Value; + type Error = Error; + + fn serialize_key(&mut self, key: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + match self { + SerializeMap::Map { next_key, .. } => { + *next_key = Some(tri!(key.serialize(MapKeySerializer))); + Ok(()) + } + } + } + + fn serialize_value(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + match self { + SerializeMap::Map { map, next_key } => { + let key = next_key.take(); + // Panic because this indicates a bug in the program rather than an + // expected failure. + let key = key.expect("serialize_value called before serialize_key"); + map.insert(key, tri!(to_value(value))); + Ok(()) + } + } + } + + fn end(self) -> Result { + match self { + SerializeMap::Map { map, .. } => Ok(Value::Object(map)), + } + } +} + +impl serde::ser::SerializeStruct for SerializeMap { + type Ok = Value; + type Error = Error; + + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + match self { + SerializeMap::Map { .. } => serde::ser::SerializeMap::serialize_entry(self, key, value), + } + } + + fn end(self) -> Result { + match self { + SerializeMap::Map { .. } => serde::ser::SerializeMap::end(self), + } + } +} + +pub struct SerializeStructVariant { + name: String, + map: Object, +} + +impl serde::ser::SerializeStructVariant for SerializeStructVariant { + type Ok = Value; + type Error = Error; + + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + self.map.insert(String::from(key), tri!(to_value(value))); + Ok(()) + } + + fn end(self) -> Result { + let mut object = Object::new(); + + object.insert(self.name, Value::Object(self.map)); + + Ok(Value::Object(object)) + } +} + +impl serde::Serializer for Serializer { + type Ok = Value; + type Error = Error; + + type SerializeSeq = SerializeVec; + type SerializeTuple = SerializeVec; + type SerializeTupleStruct = SerializeVec; + type SerializeTupleVariant = SerializeTupleVariant; + type SerializeMap = SerializeMap; + type SerializeStruct = SerializeMap; + type SerializeStructVariant = SerializeStructVariant; + + #[inline] + fn serialize_bool(self, value: bool) -> Result { + Ok(Value::Boolean(value)) + } + + #[inline] + fn serialize_i8(self, value: i8) -> Result { + self.serialize_i64(value as i64) + } + + #[inline] + fn serialize_i16(self, value: i16) -> Result { + self.serialize_i64(value as i64) + } + + #[inline] + fn serialize_i32(self, value: i32) -> Result { + self.serialize_i64(value as i64) + } + + fn serialize_i64(self, value: i64) -> Result { + Ok(Value::Integer(value.into())) + } + + #[inline] + fn serialize_u8(self, value: u8) -> Result { + self.serialize_u64(value as u64) + } + + #[inline] + fn serialize_u16(self, value: u16) -> Result { + self.serialize_u64(value as u64) + } + + #[inline] + fn serialize_u32(self, value: u32) -> Result { + self.serialize_u64(value as u64) + } + + #[inline] + fn serialize_u64(self, value: u64) -> Result { + Ok(Value::Integer(value.try_into()?)) + } + + #[inline] + fn serialize_f32(self, float: f32) -> Result { + Ok(Value::Float(float.into())) + } + + #[inline] + fn serialize_f64(self, float: f64) -> Result { + Ok(Value::Float(float)) + } + + #[inline] + fn serialize_char(self, value: char) -> Result { + let mut s = String::new(); + s.push(value); + Ok(Value::String(s)) + } + + #[inline] + fn serialize_str(self, value: &str) -> Result { + Ok(Value::String(value.to_owned())) + } + + fn serialize_bytes(self, value: &[u8]) -> Result { + let vec = value.to_owned(); + Ok(Value::Bytes(vec)) + } + + #[inline] + fn serialize_unit(self) -> Result { + Ok(Value::Unit) + } + + #[inline] + fn serialize_unit_struct(self, _name: &'static str) -> Result { + self.serialize_unit() + } + + #[inline] + fn serialize_unit_variant(self, _name: &'static str, _variant_index: u32, variant: &'static str) -> Result { + self.serialize_str(variant) + } + + #[inline] + fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result + where + T: ?Sized + Serialize, + { + let mut values = Object::default(); + values.insert(String::from(variant), to_value(value)?); + Ok(Value::Object(values)) + } + #[inline] + fn serialize_none(self) -> Result { + self.serialize_unit() + } + + #[inline] + fn serialize_some(self, value: &T) -> Result + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + fn serialize_seq(self, len: Option) -> Result { + Ok(SerializeVec { + vec: Vec::with_capacity(len.unwrap_or(0)), + }) + } + + fn serialize_tuple(self, len: usize) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_tuple_struct(self, _name: &'static str, len: usize) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + Ok(SerializeTupleVariant { + name: String::from(variant), + vec: Vec::with_capacity(len), + }) + } + + fn serialize_map(self, _len: Option) -> Result { + Ok(SerializeMap::Map { + map: Object::new(), + next_key: None, + }) + } + fn serialize_struct(self, name: &'static str, len: usize) -> Result { + match name { + _ => self.serialize_map(Some(len)), + } + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + Ok(SerializeStructVariant { + name: String::from(variant), + map: Object::new(), + }) + } + + fn collect_str(self, value: &T) -> Result + where + T: ?Sized + std::fmt::Display, + { + Ok(Value::String(value.to_string())) + } +}