From 733048fff0330fb4b1ebc5cf90b5ec25612aec6e Mon Sep 17 00:00:00 2001 From: Adam Chalmers Date: Thu, 7 Nov 2024 17:20:20 -0600 Subject: [PATCH] Switch everything away from UserVal --- src/wasm-lib/kcl/src/ast/types.rs | 49 ++- src/wasm-lib/kcl/src/ast/types/execute.rs | 190 +++++------- src/wasm-lib/kcl/src/ast/types/none.rs | 20 +- src/wasm-lib/kcl/src/executor.rs | 358 ++++++---------------- src/wasm-lib/kcl/src/std/args.rs | 309 ++++++++++++------- src/wasm-lib/kcl/src/std/array.rs | 33 +- src/wasm-lib/kcl/src/std/assert.rs | 12 +- src/wasm-lib/kcl/src/std/extrude.rs | 6 +- src/wasm-lib/kcl/src/std/fillet.rs | 35 +-- src/wasm-lib/kcl/src/std/math.rs | 44 +-- src/wasm-lib/kcl/src/std/mod.rs | 6 +- src/wasm-lib/kcl/src/std/patterns.rs | 89 +++--- src/wasm-lib/kcl/src/std/segment.rs | 20 +- src/wasm-lib/kcl/src/std/sketch.rs | 16 +- src/wasm-lib/kcl/src/std/units.rs | 12 +- 15 files changed, 516 insertions(+), 683 deletions(-) diff --git a/src/wasm-lib/kcl/src/ast/types.rs b/src/wasm-lib/kcl/src/ast/types.rs index 01a2d99377..59a03554c8 100644 --- a/src/wasm-lib/kcl/src/ast/types.rs +++ b/src/wasm-lib/kcl/src/ast/types.rs @@ -27,7 +27,7 @@ pub use crate::ast::types::{ use crate::{ docs::StdLibFn, errors::KclError, - executor::{ExecState, ExecutorContext, KclValue, Metadata, SourceRange, TagIdentifier, UserVal}, + executor::{ExecState, ExecutorContext, KclValue, Metadata, SourceRange, TagIdentifier}, parser::PIPE_OPERATOR, std::kcl_stdlib::KclStdLibFn, }; @@ -59,6 +59,14 @@ pub struct Node { pub module_id: ModuleId, } +impl Node { + pub fn metadata(&self) -> Metadata { + Metadata { + source_range: SourceRange([self.start, self.end, self.module_id.0 as usize]), + } + } +} + impl schemars::JsonSchema for Node { fn schema_name() -> String { T::schema_name() @@ -1708,34 +1716,26 @@ impl Literal { impl From> for KclValue { fn from(literal: Node) -> Self { - KclValue::UserVal(UserVal { - value: JValue::from(literal.value.clone()), - meta: vec![Metadata { - source_range: literal.into(), - }], - }) + let meta = vec![literal.metadata()]; + match literal.inner.value { + LiteralValue::IInteger(value) => KclValue::Int { value, meta }, + LiteralValue::Fractional(value) => KclValue::Number { value, meta }, + LiteralValue::String(value) => KclValue::String { value, meta }, + LiteralValue::Bool(value) => KclValue::Bool { value, meta }, + } } } impl From<&Node> for KclValue { fn from(literal: &Node) -> Self { - KclValue::UserVal(UserVal { - value: JValue::from(literal.value.clone()), - meta: vec![Metadata { - source_range: literal.into(), - }], - }) + Self::from(literal.to_owned()) } } impl From<&BoxNode> for KclValue { fn from(literal: &BoxNode) -> Self { - KclValue::UserVal(UserVal { - value: JValue::from(literal.value.clone()), - meta: vec![Metadata { - source_range: literal.into(), - }], - }) + let b: &Node = literal; + Self::from(b) } } @@ -3010,17 +3010,6 @@ impl ConstraintLevels { } } -pub(crate) fn human_friendly_type(j: &JValue) -> &'static str { - match j { - JValue::Null => "null", - JValue::Bool(_) => "boolean (true/false value)", - JValue::Number(_) => "number", - JValue::String(_) => "string (text)", - JValue::Array(_) => "array (list)", - JValue::Object(_) => "object", - } -} - #[cfg(test)] mod tests { use pretty_assertions::assert_eq; diff --git a/src/wasm-lib/kcl/src/ast/types/execute.rs b/src/wasm-lib/kcl/src/ast/types/execute.rs index 36a7804e2b..a68052a099 100644 --- a/src/wasm-lib/kcl/src/ast/types/execute.rs +++ b/src/wasm-lib/kcl/src/ast/types/execute.rs @@ -1,18 +1,21 @@ +use std::collections::HashMap; + use super::{ - human_friendly_type, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, - CallExpression, Expr, IfExpression, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, - ObjectExpression, TagDeclarator, UnaryExpression, UnaryOperator, + ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, CallExpression, Expr, + IfExpression, KclNone, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, ObjectExpression, + TagDeclarator, UnaryExpression, UnaryOperator, }; use crate::{ errors::{KclError, KclErrorDetails}, executor::{ BodyType, ExecState, ExecutorContext, KclValue, Metadata, SourceRange, StatementKind, TagEngineInfo, - TagIdentifier, UserVal, + TagIdentifier, }, std::FunctionKind, }; use async_recursion::async_recursion; -use serde_json::Value as JValue; + +const FLOAT_TO_INT_MAX_DELTA: f64 = 0.01; impl BinaryPart { #[async_recursion] @@ -73,7 +76,7 @@ impl Node { // Check the property and object match -- e.g. ints for arrays, strs for objects. match (object, property) { - (KclValue::Object { value: map, meta }, Property::String(property)) => { + (KclValue::Object { value: map, meta: _ }, Property::String(property)) => { if let Some(value) = map.get(&property) { Ok(value.to_owned()) } else { @@ -90,7 +93,7 @@ impl Node { ), source_ranges: vec![self.clone().into()], })), - (KclValue::Array { value: arr, meta }, Property::Number(index)) => { + (KclValue::Array { value: arr, meta: _ }, Property::Number(index)) => { let value_of_arr = arr.get(index); if let Some(value) = value_of_arr { Ok(value.to_owned()) @@ -124,22 +127,14 @@ impl Node { pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result { let left_value = self.left.get_result(exec_state, ctx).await?; let right_value = self.right.get_result(exec_state, ctx).await?; + let mut meta = left_value.metadata(); + meta.extend(right_value.metadata()); // First check if we are doing string concatenation. if self.operator == BinaryOperator::Add { - if let ( - KclValue::String { - value: left, - meta: meta_l, - }, - KclValue::String { - value: right, - meta: meta_r, - }, - ) = (left_value, right_value) + if let (KclValue::String { value: left, meta: _ }, KclValue::String { value: right, meta: _ }) = + (&left_value, &right_value) { - let mut meta = meta_l; - meta_l.extend(meta_r); return Ok(KclValue::String { value: format!("{}{}", left, right), meta, @@ -150,9 +145,7 @@ impl Node { let left = parse_number_as_f64(&left_value, self.left.clone().into())?; let right = parse_number_as_f64(&right_value, self.right.clone().into())?; - let mut meta = self.left.into(); - meta.extend(self.right.into()); - let value: f64 = match self.operator { + let value = match self.operator { BinaryOperator::Add => KclValue::Number { value: left + right, meta, @@ -457,10 +450,10 @@ impl Node { } else { fn_memory.add( ¶m.identifier.name, - KclValue::UserVal(UserVal { - value: serde_json::value::Value::Null, - meta: Default::default(), - }), + KclValue::KclNone { + value: KclNone::new(), + meta: vec![self.into()], + }, param.identifier.clone().into(), )?; } @@ -563,15 +556,13 @@ impl Node { .execute_expr(element, exec_state, &metadata, StatementKind::Expression) .await?; - results.push(value.get_json_value()?); + results.push(value); } - Ok(KclValue::UserVal(UserVal { - value: results.into(), - meta: vec![Metadata { - source_range: self.into(), - }], - })) + Ok(KclValue::Array { + value: results, + meta: vec![self.into()], + }) } } @@ -581,15 +572,19 @@ impl Node { let metadata = Metadata::from(&self.start_element); let start = ctx .execute_expr(&self.start_element, exec_state, &metadata, StatementKind::Expression) - .await? - .get_json_value()?; - let start = parse_json_number_as_i64(&start, (&self.start_element).into())?; + .await?; + let start = start.as_int().ok_or(KclError::Semantic(KclErrorDetails { + source_ranges: vec![self.into()], + message: format!("Expected int but found {}", start.human_friendly_type()), + }))?; let metadata = Metadata::from(&self.end_element); let end = ctx .execute_expr(&self.end_element, exec_state, &metadata, StatementKind::Expression) - .await? - .get_json_value()?; - let end = parse_json_number_as_i64(&end, (&self.end_element).into())?; + .await?; + let end = end.as_int().ok_or(KclError::Semantic(KclErrorDetails { + source_ranges: vec![self.into()], + message: format!("Expected int but found {}", end.human_friendly_type()), + }))?; if end < start { return Err(KclError::Semantic(KclErrorDetails { @@ -599,55 +594,46 @@ impl Node { } let range: Vec<_> = if self.end_inclusive { - (start..=end).map(JValue::from).collect() + (start..=end).collect() } else { - (start..end).map(JValue::from).collect() + (start..end).collect() }; - Ok(KclValue::UserVal(UserVal { - value: range.into(), - meta: vec![Metadata { - source_range: self.into(), - }], - })) + let meta = vec![Metadata { + source_range: self.into(), + }]; + Ok(KclValue::Array { + value: range + .into_iter() + .map(|num| KclValue::Int { + value: num, + meta: meta.clone(), + }) + .collect(), + meta, + }) } } impl Node { #[async_recursion] pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result { - let mut object = serde_json::Map::new(); + let mut object = HashMap::with_capacity(self.properties.len()); for property in &self.properties { let metadata = Metadata::from(&property.value); let result = ctx .execute_expr(&property.value, exec_state, &metadata, StatementKind::Expression) .await?; - object.insert(property.key.name.clone(), result.get_json_value()?); + object.insert(property.key.name.clone(), result); } - Ok(KclValue::UserVal(UserVal { - value: object.into(), + Ok(KclValue::Object { + value: object, meta: vec![Metadata { source_range: self.into(), }], - })) - } -} - -fn parse_json_number_as_i64(j: &serde_json::Value, source_range: SourceRange) -> Result { - if let serde_json::Value::Number(n) = &j { - n.as_i64().ok_or_else(|| { - KclError::Syntax(KclErrorDetails { - source_ranges: vec![source_range], - message: format!("Invalid integer: {}", j), - }) }) - } else { - Err(KclError::Syntax(KclErrorDetails { - source_ranges: vec![source_range], - message: format!("Invalid integer: {}", j), - })) } } @@ -657,31 +643,11 @@ pub fn parse_number_as_f64(v: &KclValue, source_range: SourceRange) -> Result Option { - if let serde_json::Value::String(n) = &j { - Some(n.clone()) - } else { - None - } -} - -/// JSON value as bool. If it isn't a bool, returns None. -pub fn json_as_bool(j: &serde_json::Value) -> Option { - match j { - JValue::Null => None, - JValue::Bool(b) => Some(*b), - JValue::Number(_) => None, - JValue::String(_) => None, - JValue::Array(_) => None, - JValue::Object(_) => None, - } -} - impl Node { #[async_recursion] pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result { @@ -751,15 +717,7 @@ impl Property { } else { // Actually evaluate memory to compute the property. let prop = exec_state.memory.get(name, property_src)?; - let KclValue::UserVal(prop) = prop else { - return Err(KclError::Semantic(KclErrorDetails { - source_ranges: property_sr, - message: format!( - "{name} is not a valid property/index, you can only use a string or int (>= 0) here", - ), - })); - }; - jvalue_to_prop(&prop.value, property_sr, name) + jvalue_to_prop(prop, property_sr, name) } } LiteralIdentifier::Literal(literal) => { @@ -786,35 +744,37 @@ impl Property { } } -fn jvalue_to_prop(value: &JValue, property_sr: Vec, name: &str) -> Result { +fn jvalue_to_prop(value: &KclValue, property_sr: Vec, name: &str) -> Result { let make_err = |message: String| { Err::(KclError::Semantic(KclErrorDetails { source_ranges: property_sr, message, })) }; - const MUST_BE_POSINT: &str = "indices must be whole positive numbers"; - const TRY_INT: &str = "try using the int() function to make this a whole number"; match value { - JValue::Number(ref num) => { - let maybe_uint = num.as_u64().and_then(|x| usize::try_from(x).ok()); - if let Some(uint) = maybe_uint { + KclValue::Int { value:num, meta: _ } => { + let maybe_int: Result = (*num).try_into(); + if let Ok(uint) = maybe_int { Ok(Property::Number(uint)) - } else if let Some(iint) = num.as_i64() { - make_err(format!("'{iint}' is not a valid index, {MUST_BE_POSINT}")) - } else if let Some(fnum) = num.as_f64() { - if fnum < 0.0 { - make_err(format!("'{fnum}' is not a valid index, {MUST_BE_POSINT}")) - } else if fnum.fract() == 0.0 { - make_err(format!("'{fnum:.1}' is stored as a fractional number but indices must be whole numbers, {TRY_INT}")) - } else { - make_err(format!("'{fnum}' is not a valid index, {MUST_BE_POSINT}, {TRY_INT}")) - } + } + else { + make_err(format!("'{num}' is negative, so you can't index an array with it")) + } + } + KclValue::Number{value: num, meta:_} => { + let num = *num; + if num < 0.0 { + return make_err(format!("'{num}' is negative, so you can't index an array with it")) + } + let nearest_int = num.round(); + let delta = num-nearest_int; + if delta < FLOAT_TO_INT_MAX_DELTA { + Ok(Property::Number(nearest_int as usize)) } else { - make_err(format!("'{num}' is not a valid index, {MUST_BE_POSINT}")) + make_err(format!("'{num}' is not an integer, so you can't index an array with it")) } } - JValue::String(ref x) => Ok(Property::String(x.to_owned())), + KclValue::String{value: x, meta:_} => Ok(Property::String(x.to_owned())), _ => { make_err(format!("{name} is not a valid property/index, you can only use a string to get the property of an object, or an int (>= 0) to get an item in an array")) } diff --git a/src/wasm-lib/kcl/src/ast/types/none.rs b/src/wasm-lib/kcl/src/ast/types/none.rs index 6e60243311..618ee7be5c 100644 --- a/src/wasm-lib/kcl/src/ast/types/none.rs +++ b/src/wasm-lib/kcl/src/ast/types/none.rs @@ -4,10 +4,7 @@ use databake::*; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::{ - ast::types::ConstraintLevel, - executor::{KclValue, UserVal}, -}; +use crate::{ast::types::ConstraintLevel, executor::KclValue}; use super::Node; @@ -16,7 +13,7 @@ const KCL_NONE_ID: &str = "KCL_NONE_ID"; /// KCL value for an optional parameter which was not given an argument. /// (remember, parameters are in the function declaration, /// arguments are in the function call/application). -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake, Default)] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake, Default, Copy)] #[databake(path = kcl_lib::ast::types)] #[ts(export)] #[serde(tag = "type")] @@ -58,22 +55,15 @@ where } } -impl From<&KclNone> for UserVal { +impl From<&KclNone> for KclValue { fn from(none: &KclNone) -> Self { - UserVal { - value: serde_json::to_value(none).expect("can always serialize a None"), + KclValue::KclNone { + value: *none, meta: Default::default(), } } } -impl From<&KclNone> for KclValue { - fn from(none: &KclNone) -> Self { - let val = UserVal::from(none); - KclValue::UserVal(val) - } -} - impl From<&Node> for KclValue { fn from(none: &Node) -> Self { Self::from(&none.inner) diff --git a/src/wasm-lib/kcl/src/executor.rs b/src/wasm-lib/kcl/src/executor.rs index 102f91b92a..bc0425f9f8 100644 --- a/src/wasm-lib/kcl/src/executor.rs +++ b/src/wasm-lib/kcl/src/executor.rs @@ -19,7 +19,6 @@ use kittycad_modeling_cmds::length_unit::LengthUnit; use parse_display::{Display, FromStr}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_json::Value as JValue; use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange}; type Point2D = kcmc::shared::Point2d; @@ -27,8 +26,8 @@ type Point3D = kcmc::shared::Point3d; use crate::{ ast::types::{ - human_friendly_type, BodyItem, Expr, FunctionExpression, ItemVisibility, KclNone, ModuleId, Node, NodeRef, - Program, TagDeclarator, TagNode, + BodyItem, Expr, FunctionExpression, ItemVisibility, KclNone, ModuleId, Node, NodeRef, Program, TagDeclarator, + TagNode, }, engine::{EngineManager, ExecutionKind}, errors::{KclError, KclErrorDetails}, @@ -338,6 +337,10 @@ impl IdGenerator { #[ts(export)] #[serde(tag = "type")] pub enum KclValue { + Uuid { + value: ::uuid::Uuid, + meta: Vec, + }, Bool { value: bool, meta: Vec, @@ -385,10 +388,36 @@ pub enum KclValue { #[serde(rename = "__meta")] meta: Vec, }, - KclNone, + KclNone { + value: KclNone, + #[serde(rename = "__meta")] + meta: Vec, + }, } impl KclValue { + pub(crate) fn metadata(&self) -> Vec { + match self { + KclValue::Uuid { value: _, meta } => meta.clone(), + KclValue::Bool { value: _, meta } => meta.clone(), + KclValue::Number { value: _, meta } => meta.clone(), + KclValue::Int { value: _, meta } => meta.clone(), + KclValue::String { value: _, meta } => meta.clone(), + KclValue::Array { value: _, meta } => meta.clone(), + KclValue::Object { value: _, meta } => meta.clone(), + KclValue::TagIdentifier(x) => x.meta.clone(), + KclValue::TagDeclarator(x) => vec![x.metadata()], + KclValue::Plane(x) => x.meta.clone(), + KclValue::Face(x) => x.meta.clone(), + KclValue::Sketch(x) => x.meta.clone(), + KclValue::Sketches { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(), + KclValue::Solid(x) => x.meta.clone(), + KclValue::Solids { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(), + KclValue::ImportedGeometry(x) => x.meta.clone(), + KclValue::Function { meta, .. } => meta.clone(), + KclValue::KclNone { meta, .. } => meta.clone(), + } + } pub(crate) fn get_solid_set(&self) -> Result { match self { KclValue::Solid(e) => Ok(SolidSet::Solid(e.clone())), @@ -401,6 +430,7 @@ impl KclValue { /// on for program logic. pub(crate) fn human_friendly_type(&self) -> &'static str { match self { + KclValue::Uuid { .. } => "Unique ID (uuid)", KclValue::TagDeclarator(_) => "TagDeclarator", KclValue::TagIdentifier(_) => "TagIdentifier", KclValue::Solid(_) => "Solid", @@ -417,7 +447,7 @@ impl KclValue { KclValue::String { .. } => "string", KclValue::Array { .. } => "array", KclValue::Object { .. } => "object", - KclValue::KclNone => "None", + KclValue::KclNone { .. } => "None", } } @@ -802,16 +832,6 @@ pub enum PlaneType { Custom, } -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] -#[ts(export)] -#[serde(tag = "type", rename_all = "camelCase")] -pub struct UserVal { - #[ts(type = "any")] - pub value: serde_json::Value, - #[serde(rename = "__meta")] - pub meta: Vec, -} - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)] #[ts(export)] #[serde(tag = "type", rename_all = "camelCase")] @@ -873,7 +893,7 @@ pub type MemoryFunction = impl From for Vec { fn from(item: KclValue) -> Self { match item { - KclValue::TagDeclarator(t) => vec![SourceRange([t.start, t.end])], + KclValue::TagDeclarator(t) => vec![SourceRange([t.start, t.end, t.module_id.0 as usize])], KclValue::TagIdentifier(t) => to_vec_sr(&t.meta), KclValue::Solid(e) => to_vec_sr(&e.meta), KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(), @@ -889,6 +909,8 @@ impl From for Vec { KclValue::String { meta, .. } => to_vec_sr(&meta), KclValue::Array { meta, .. } => to_vec_sr(&meta), KclValue::Object { meta, .. } => to_vec_sr(&meta), + KclValue::Uuid { meta, .. } => to_vec_sr(&meta), + KclValue::KclNone { meta, .. } => to_vec_sr(&meta), } } } @@ -900,7 +922,7 @@ fn to_vec_sr(meta: &[Metadata]) -> Vec { impl From<&KclValue> for Vec { fn from(item: &KclValue) -> Self { match item { - KclValue::TagDeclarator(t) => vec![SourceRange([t.start, t.end])], + KclValue::TagDeclarator(t) => vec![SourceRange([t.start, t.end, t.module_id.0 as usize])], KclValue::TagIdentifier(t) => to_vec_sr(&t.meta), KclValue::Solid(e) => to_vec_sr(&e.meta), KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(), @@ -914,8 +936,10 @@ impl From<&KclValue> for Vec { KclValue::Number { meta, .. } => to_vec_sr(meta), KclValue::Int { meta, .. } => to_vec_sr(meta), KclValue::String { meta, .. } => to_vec_sr(meta), + KclValue::Uuid { meta, .. } => to_vec_sr(meta), KclValue::Array { meta, .. } => to_vec_sr(meta), KclValue::Object { meta, .. } => to_vec_sr(meta), + KclValue::KclNone { meta, .. } => to_vec_sr(meta), } } } @@ -926,8 +950,39 @@ impl KclValue { Self::Number { value: f, meta } } + /// Put the point into a KCL value. + pub fn from_point2d(p: [f64; 2], meta: Vec) -> Self { + Self::Array { + value: vec![ + Self::Number { + value: p[0], + meta: meta.clone(), + }, + Self::Number { + value: p[1], + meta: meta.clone(), + }, + ], + meta, + } + } + pub fn as_int(&self) -> Option { - if let KclValue::Int { value, meta } = &self { + if let KclValue::Int { value, meta: _ } = &self { + Some(*value) + } else { + None + } + } + pub fn as_f64(&self) -> Option { + if let KclValue::Number { value, meta: _ } = &self { + Some(*value) + } else { + None + } + } + pub fn as_bool(&self) -> Option { + if let KclValue::Bool { value, meta: _ } = &self { Some(*value) } else { None @@ -1003,19 +1058,13 @@ impl KclValue { /// If this KCL value is a bool, retrieve it. pub fn get_bool(&self) -> Result { - let Self::UserVal(uv) = self else { + let Self::Bool { value: b, .. } = self else { return Err(KclError::Type(KclErrorDetails { source_ranges: self.into(), message: format!("Expected bool, found {}", self.human_friendly_type()), })); }; - let JValue::Bool(b) = uv.value else { - return Err(KclError::Type(KclErrorDetails { - source_ranges: self.into(), - message: format!("Expected bool, found {}", human_friendly_type(&uv.value)), - })); - }; - Ok(b) + Ok(*b) } /// If this memory item is a function, call it with the given arguments, return its val as Ok. @@ -1469,7 +1518,7 @@ impl From for kittycad_modeling_cmds::shared::Point3d { } /// Metadata. -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq, Copy)] #[ts(export)] #[serde(rename_all = "camelCase")] pub struct Metadata { @@ -2575,74 +2624,8 @@ mod tests { } /// Convenience function to get a JSON value from memory and unwrap. - fn mem_get_json(memory: &ProgramMemory, name: &str) -> serde_json::Value { - memory - .get(name, SourceRange::default()) - .unwrap() - .get_json_value() - .unwrap() - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_execute_assign_two_variables() { - let ast = r#"const myVar = 5 -const newVar = myVar + 1"#; - let memory = parse_execute(ast).await.unwrap(); - assert_eq!( - serde_json::json!(5), - memory - .get("myVar", SourceRange::default()) - .unwrap() - .get_json_value() - .unwrap() - ); - assert_eq!( - serde_json::json!(6.0), - memory - .get("newVar", SourceRange::default()) - .unwrap() - .get_json_value() - .unwrap() - ); - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_execute_angled_line_that_intersects() { - let ast_fn = |offset: &str| -> String { - format!( - r#"const part001 = startSketchOn('XY') - |> startProfileAt([0, 0], %) - |> lineTo([2, 2], %, $yo) - |> lineTo([3, 1], %) - |> angledLineThatIntersects({{ - angle: 180, - intersectTag: yo, - offset: {}, -}}, %, $yo2) -const intersect = segEndX(yo2)"#, - offset - ) - }; - - let memory = parse_execute(&ast_fn("-1")).await.unwrap(); - assert_eq!( - serde_json::json!(1.0 + 2.0f64.sqrt()), - memory - .get("intersect", SourceRange::default()) - .unwrap() - .get_json_value() - .unwrap() - ); - - let memory = parse_execute(&ast_fn("0")).await.unwrap(); - assert_eq!( - serde_json::json!(1.0000000000000002), - memory - .get("intersect", SourceRange::default()) - .unwrap() - .get_json_value() - .unwrap() - ); + fn mem_get_json(memory: &ProgramMemory, name: &str) -> KclValue { + memory.get(name, SourceRange::default()).unwrap().to_owned() } #[tokio::test(flavor = "multi_thread")] @@ -3040,200 +3023,41 @@ let shape = layer() |> patternTransform(10, transform, %) ); } - #[tokio::test(flavor = "multi_thread")] - async fn test_execute_function_with_parameter_redefined_outside() { - let ast = r#" -fn myIdentity = (x) => { - return x -} - -const x = 33 - -const two = myIdentity(2)"#; - - let memory = parse_execute(ast).await.unwrap(); - assert_eq!( - serde_json::json!(2), - memory - .get("two", SourceRange::default()) - .unwrap() - .get_json_value() - .unwrap() - ); - assert_eq!( - serde_json::json!(33), - memory - .get("x", SourceRange::default()) - .unwrap() - .get_json_value() - .unwrap() - ); - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_execute_function_referencing_variable_in_parent_scope() { - let ast = r#" -const x = 22 -const y = 3 - -fn add = (x) => { - return x + y -} - -const answer = add(2)"#; - - let memory = parse_execute(ast).await.unwrap(); - assert_eq!( - serde_json::json!(5.0), - memory - .get("answer", SourceRange::default()) - .unwrap() - .get_json_value() - .unwrap() - ); - assert_eq!( - serde_json::json!(22), - memory - .get("x", SourceRange::default()) - .unwrap() - .get_json_value() - .unwrap() - ); - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_execute_function_redefining_variable_in_parent_scope() { - let ast = r#" -const x = 1 - -fn foo = () => { - const x = 2 - return x -} - -const answer = foo()"#; - - let memory = parse_execute(ast).await.unwrap(); - assert_eq!( - serde_json::json!(2), - memory - .get("answer", SourceRange::default()) - .unwrap() - .get_json_value() - .unwrap() - ); - assert_eq!( - serde_json::json!(1), - memory - .get("x", SourceRange::default()) - .unwrap() - .get_json_value() - .unwrap() - ); - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_execute_pattern_transform_function_redefining_variable_in_parent_scope() { - let ast = r#" -const scale = 100 -fn transform = (replicaId) => { - // Redefine same variable as in parent scope. - const scale = 2 - return { - translate: [0, 0, replicaId * 10], - scale: [scale, 1, 0], - } -} - -fn layer = () => { - return startSketchOn("XY") - |> circle({ center: [0, 0], radius: 1 }, %, $tag1) - |> extrude(10, %) -} - -// The 10 layers are replicas of each other, with a transform applied to each. -let shape = layer() |> patternTransform(10, transform, %)"#; - - let memory = parse_execute(ast).await.unwrap(); - // TODO: Assert that scale 2 was used. - assert_eq!( - serde_json::json!(100), - memory - .get("scale", SourceRange::default()) - .unwrap() - .get_json_value() - .unwrap() - ); - } + // ADAM: Move some of these into simulation tests. #[tokio::test(flavor = "multi_thread")] async fn test_math_execute_with_functions() { let ast = r#"const myVar = 2 + min(100, -1 + legLen(5, 3))"#; let memory = parse_execute(ast).await.unwrap(); - assert_eq!( - serde_json::json!(5.0), - memory - .get("myVar", SourceRange::default()) - .unwrap() - .get_json_value() - .unwrap() - ); + assert_eq!(5.0, mem_get_json(&memory, "myVar").as_f64().unwrap()); } #[tokio::test(flavor = "multi_thread")] async fn test_math_execute() { let ast = r#"const myVar = 1 + 2 * (3 - 4) / -5 + 6"#; let memory = parse_execute(ast).await.unwrap(); - assert_eq!( - serde_json::json!(7.4), - memory - .get("myVar", SourceRange::default()) - .unwrap() - .get_json_value() - .unwrap() - ); + assert_eq!(7.5, mem_get_json(&memory, "myVar").as_f64().unwrap()); } #[tokio::test(flavor = "multi_thread")] async fn test_math_execute_start_negative() { let ast = r#"const myVar = -5 + 6"#; let memory = parse_execute(ast).await.unwrap(); - assert_eq!( - serde_json::json!(1.0), - memory - .get("myVar", SourceRange::default()) - .unwrap() - .get_json_value() - .unwrap() - ); + assert_eq!(-1.0, mem_get_json(&memory, "myVar").as_f64().unwrap()); } #[tokio::test(flavor = "multi_thread")] async fn test_math_execute_with_pi() { let ast = r#"const myVar = pi() * 2"#; let memory = parse_execute(ast).await.unwrap(); - assert_eq!( - serde_json::json!(std::f64::consts::TAU), - memory - .get("myVar", SourceRange::default()) - .unwrap() - .get_json_value() - .unwrap() - ); + assert_eq!(std::f64::consts::TAU, mem_get_json(&memory, "myVar").as_f64().unwrap()); } #[tokio::test(flavor = "multi_thread")] async fn test_math_define_decimal_without_leading_zero() { let ast = r#"let thing = .4 + 7"#; let memory = parse_execute(ast).await.unwrap(); - assert_eq!( - serde_json::json!(7.4), - memory - .get("thing", SourceRange::default()) - .unwrap() - .get_json_value() - .unwrap() - ); + assert_eq!(7.4, mem_get_json(&memory, "thing").as_f64().unwrap()); } #[tokio::test(flavor = "multi_thread")] @@ -3273,10 +3097,10 @@ fn check = (x) => { check(false) "#; let mem = parse_execute(ast).await.unwrap(); - assert_eq!(serde_json::json!(false), mem_get_json(&mem, "notTrue")); - assert_eq!(serde_json::json!(true), mem_get_json(&mem, "notFalse")); - assert_eq!(serde_json::json!(true), mem_get_json(&mem, "c")); - assert_eq!(serde_json::json!(false), mem_get_json(&mem, "d")); + assert_eq!(false, mem_get_json(&mem, "notTrue").as_bool().unwrap()); + assert_eq!(true, mem_get_json(&mem, "notFalse").as_bool().unwrap()); + assert_eq!(true, mem_get_json(&mem, "c").as_bool().unwrap()); + assert_eq!(false, mem_get_json(&mem, "d").as_bool().unwrap()); } #[tokio::test(flavor = "multi_thread")] @@ -3523,10 +3347,10 @@ let w = f() + f() fn test_assign_args_to_params() { // Set up a little framework for this test. fn mem(number: usize) -> KclValue { - KclValue::UserVal(UserVal { - value: number.into(), + KclValue::Int { + value: number as i64, meta: Default::default(), - }) + } } fn ident(s: &'static str) -> Node { Node::no_src(Identifier { diff --git a/src/wasm-lib/kcl/src/std/args.rs b/src/wasm-lib/kcl/src/std/args.rs index 42a04b7c41..f21c30653a 100644 --- a/src/wasm-lib/kcl/src/std/args.rs +++ b/src/wasm-lib/kcl/src/std/args.rs @@ -3,15 +3,13 @@ use std::any::type_name; use anyhow::Result; use kcmc::{websocket::OkWebSocketResponseData, ModelingCmd}; use kittycad_modeling_cmds as kcmc; -use serde::de::DeserializeOwned; -use serde_json::Value as JValue; use crate::{ - ast::types::{execute::parse_json_number_as_f64, TagNode}, + ast::types::TagNode, errors::{KclError, KclErrorDetails}, executor::{ ExecState, ExecutorContext, ExtrudeSurface, KclValue, Metadata, Sketch, SketchSet, SketchSurface, Solid, - SolidSet, SourceRange, TagIdentifier, UserVal, + SolidSet, SourceRange, TagIdentifier, }, std::{shapes::SketchOrSurface, sketch::FaceTag, FnAsArg}, }; @@ -181,39 +179,58 @@ impl Args { Ok(()) } - fn make_user_val_from_json(&self, j: serde_json::Value) -> KclValue { - KclValue::UserVal(crate::executor::UserVal { - value: j, - meta: vec![Metadata { - source_range: self.source_range, - }], + pub(crate) fn make_user_val_from_point(&self, p: [f64; 2]) -> Result { + let meta = Metadata { + source_range: self.source_range, + }; + let x = KclValue::Number { + value: p[0], + meta: vec![meta], + }; + let y = KclValue::Number { + value: p[1], + meta: vec![meta], + }; + Ok(KclValue::Array { + value: vec![x, y], + meta: vec![meta], }) } - pub(crate) fn make_null_user_val(&self) -> KclValue { - self.make_user_val_from_json(serde_json::Value::Null) + pub(crate) fn make_user_val_from_f64(&self, f: f64) -> KclValue { + KclValue::from_number( + f, + vec![Metadata { + source_range: self.source_range, + }], + ) } pub(crate) fn make_user_val_from_i64(&self, n: i64) -> KclValue { - self.make_user_val_from_json(serde_json::Value::Number(serde_json::Number::from(n))) - } - - pub(crate) fn make_user_val_from_f64(&self, f: f64) -> Result { - f64_to_jnum(f, vec![self.source_range]).map(|x| self.make_user_val_from_json(x)) - } - - pub(crate) fn make_user_val_from_point(&self, p: [f64; 2]) -> Result { - let x = f64_to_jnum(p[0], vec![self.source_range])?; - let y = f64_to_jnum(p[1], vec![self.source_range])?; - let array = serde_json::Value::Array(vec![x, y]); - Ok(self.make_user_val_from_json(array)) + KclValue::Int { + value: n, + meta: vec![Metadata { + source_range: self.source_range, + }], + } } pub(crate) fn make_user_val_from_f64_array(&self, f: Vec) -> Result { - f.into_iter() - .map(|n| f64_to_jnum(n, vec![self.source_range])) - .collect::, _>>() - .map(|arr| self.make_user_val_from_json(serde_json::Value::Array(arr))) + let array = f + .into_iter() + .map(|n| KclValue::Number { + value: n, + meta: vec![Metadata { + source_range: self.source_range, + }], + }) + .collect::>(); + Ok(KclValue::Array { + value: array, + meta: vec![Metadata { + source_range: self.source_range, + }], + }) } pub(crate) fn get_number(&self) -> Result { @@ -221,11 +238,19 @@ impl Args { } pub(crate) fn get_number_array(&self) -> Result, KclError> { - let mut numbers: Vec = Vec::new(); - for arg in &self.args { - let parsed = arg.get_json_value()?; - numbers.push(parse_json_number_as_f64(&parsed, self.source_range)?); - } + let numbers = self + .args + .iter() + .map(|arg| { + let KclValue::Number { value: num, meta: _ } = arg else { + return Err(KclError::Semantic(KclErrorDetails { + source_ranges: arg.metadata().iter().map(|x| x.source_range).collect(), + message: format!("Expected a number but found {}", arg.human_friendly_type()), + })); + }; + Ok(*num) + }) + .collect::>()?; Ok(numbers) } @@ -474,6 +499,59 @@ pub trait FromKclValue<'a>: Sized { fn from_mem_item(arg: &'a KclValue) -> Option; } +impl<'a> FromArgs<'a> for Vec { + fn from_args(args: &'a Args, i: usize) -> Result { + let Some(arg) = args.args.get(i) else { + return Err(KclError::Semantic(KclErrorDetails { + message: format!("Expected an argument at index {i}"), + source_ranges: vec![args.source_range], + })); + }; + let KclValue::Array { value: array, meta: _ } = arg else { + let message = format!("Expected an array but found {}", arg.human_friendly_type()); + return Err(KclError::Type(KclErrorDetails { + source_ranges: arg.metadata().into_iter().map(|m| m.source_range).collect(), + message, + })); + }; + Ok(array.to_owned()) + } +} +impl<'a> FromArgs<'a> for Vec { + fn from_args(args: &'a Args, i: usize) -> Result { + let Some(arg) = args.args.get(i) else { + return Err(KclError::Semantic(KclErrorDetails { + message: format!("Expected an argument at index {i}"), + source_ranges: vec![args.source_range], + })); + }; + let message = format!("Expected an array but found {}", arg.human_friendly_type()); + let e = Err(KclError::Type(KclErrorDetails { + source_ranges: arg.metadata().into_iter().map(|m| m.source_range).collect(), + message, + })); + let array = match arg { + KclValue::Array { value, meta } => value + .iter() + .map(|val| { + if let KclValue::Sketch(sk) = val { + Ok(sk.as_ref().to_owned()) + } else { + Err(KclError::Type(KclErrorDetails { + source_ranges: meta.iter().map(|m| m.source_range).collect(), + message: format!("Expected an array but found {}", arg.human_friendly_type()), + })) + } + }) + .collect::>(), + KclValue::Sketch(sk) => Ok(vec![sk.as_ref().to_owned()]), + KclValue::Sketches { value } => Ok(value.iter().map(|sk| sk.as_ref().to_owned()).collect()), + _ => e, + }?; + Ok(array) + } +} + impl<'a, T> FromArgs<'a> for T where T: FromKclValue<'a> + Sized, @@ -563,31 +641,18 @@ where } } -impl<'a> FromKclValue<'a> for &'a str { +impl<'a> FromKclValue<'a> for [f64; 2] { fn from_mem_item(arg: &'a KclValue) -> Option { - arg.as_user_val().and_then(|uv| uv.value.as_str()) - } -} - -impl<'a> FromKclValue<'a> for i64 { - fn from_mem_item(arg: &'a KclValue) -> Option { - arg.as_user_val() - .and_then(|uv| uv.value.as_number()) - .and_then(|num| num.as_i64()) - } -} - -impl<'a> FromKclValue<'a> for UserVal { - fn from_mem_item(arg: &'a KclValue) -> Option { - arg.as_user_val().map(|x| x.to_owned()) - } -} - -impl<'a> FromKclValue<'a> for Vec { - fn from_mem_item(arg: &'a KclValue) -> Option { - arg.as_user_val() - .and_then(|uv| uv.value.as_array()) - .map(ToOwned::to_owned) + let KclValue::Array { value, meta: _ } = arg else { + return None; + }; + if value.len() != 2 { + return None; + } + let v0 = value.first()?; + let v1 = value.get(1)?; + let array = [v0.as_f64()?, v1.as_f64()?]; + Some(array) } } @@ -612,43 +677,13 @@ impl<'a> FromKclValue<'a> for KclValue { macro_rules! impl_from_arg_via_json { ($typ:path) => { impl<'a> FromKclValue<'a> for $typ { - fn from_mem_item(arg: &'a KclValue) -> Option { - from_user_val(arg) + fn from_mem_item(_arg: &'a KclValue) -> Option { + todo!("Deserialize Rust types from KCL objects") } } }; } -impl<'a, T> FromKclValue<'a> for Vec -where - T: serde::de::DeserializeOwned + FromKclValue<'a>, -{ - fn from_mem_item(arg: &'a KclValue) -> Option { - from_user_val(arg) - } -} - -macro_rules! impl_from_arg_for_array { - ($n:literal) => { - impl<'a, T> FromKclValue<'a> for [T; $n] - where - T: serde::de::DeserializeOwned + FromKclValue<'a>, - { - fn from_mem_item(arg: &'a KclValue) -> Option { - from_user_val(arg) - } - } - }; -} - -fn from_user_val(arg: &KclValue) -> Option { - let v = match arg { - KclValue::UserVal(v) => v.value.clone(), - other => serde_json::to_value(other).ok()?, - }; - serde_json::from_value(v).ok() -} - impl_from_arg_via_json!(super::sketch::AngledLineData); impl_from_arg_via_json!(super::sketch::AngledLineToData); impl_from_arg_via_json!(super::sketch::AngledLineThatIntersectsData); @@ -672,17 +707,72 @@ impl_from_arg_via_json!(crate::std::polar::PolarCoordsData); impl_from_arg_via_json!(crate::std::loft::LoftData); impl_from_arg_via_json!(crate::std::planes::StandardPlane); impl_from_arg_via_json!(crate::std::mirror::Mirror2dData); -impl_from_arg_via_json!(Sketch); impl_from_arg_via_json!(FaceTag); -impl_from_arg_via_json!(String); -impl_from_arg_via_json!(crate::ast::types::KclNone); -impl_from_arg_via_json!(u32); -impl_from_arg_via_json!(u64); -impl_from_arg_via_json!(f64); -impl_from_arg_via_json!(bool); -impl_from_arg_for_array!(2); -impl_from_arg_for_array!(3); +impl<'a> FromKclValue<'a> for i64 { + fn from_mem_item(arg: &'a KclValue) -> Option { + let KclValue::Int { value, meta: _ } = arg else { + return None; + }; + Some(*value) + } +} +impl<'a> FromKclValue<'a> for u32 { + fn from_mem_item(arg: &'a KclValue) -> Option { + let KclValue::Int { value, meta: _ } = arg else { + return None; + }; + Some(*value as u32) + } +} +impl<'a> FromKclValue<'a> for u64 { + fn from_mem_item(arg: &'a KclValue) -> Option { + let KclValue::Int { value, meta: _ } = arg else { + return None; + }; + Some(*value as u64) + } +} +impl<'a> FromKclValue<'a> for f64 { + fn from_mem_item(arg: &'a KclValue) -> Option { + let KclValue::Number { value, meta: _ } = arg else { + return None; + }; + Some(*value) + } +} +impl<'a> FromKclValue<'a> for Sketch { + fn from_mem_item(arg: &'a KclValue) -> Option { + let KclValue::Sketch(value) = arg else { + return None; + }; + Some(value.as_ref().to_owned()) + } +} +impl<'a> FromKclValue<'a> for String { + fn from_mem_item(arg: &'a KclValue) -> Option { + let KclValue::String { value, meta: _ } = arg else { + return None; + }; + Some(value.to_owned()) + } +} +impl<'a> FromKclValue<'a> for crate::ast::types::KclNone { + fn from_mem_item(arg: &'a KclValue) -> Option { + let KclValue::KclNone { value, meta: _ } = arg else { + return None; + }; + Some(value.to_owned()) + } +} +impl<'a> FromKclValue<'a> for bool { + fn from_mem_item(arg: &'a KclValue) -> Option { + let KclValue::Bool { value, meta: _ } = arg else { + return None; + }; + Some(*value) + } +} impl<'a> FromKclValue<'a> for SketchSet { fn from_mem_item(arg: &'a KclValue) -> Option { @@ -734,13 +824,18 @@ impl<'a> FromKclValue<'a> for SketchSurface { } } -fn f64_to_jnum(f: f64, source_ranges: Vec) -> Result { - serde_json::Number::from_f64(f) - .ok_or_else(|| { - KclError::Type(KclErrorDetails { - message: format!("Failed to convert `{f}` to a number"), - source_ranges, - }) - }) - .map(serde_json::Value::Number) +impl From for Metadata { + fn from(value: Args) -> Self { + Self { + source_range: value.source_range, + } + } +} + +impl From for Vec { + fn from(value: Args) -> Self { + vec![Metadata { + source_range: value.source_range, + }] + } } diff --git a/src/wasm-lib/kcl/src/std/array.rs b/src/wasm-lib/kcl/src/std/array.rs index c67092cb41..f96966f57c 100644 --- a/src/wasm-lib/kcl/src/std/array.rs +++ b/src/wasm-lib/kcl/src/std/array.rs @@ -1,37 +1,25 @@ use derive_docs::stdlib; -use serde_json::Value as JValue; use super::{args::FromArgs, Args, FnAsArg}; use crate::{ errors::{KclError, KclErrorDetails}, - executor::{ExecState, KclValue, SourceRange, UserVal}, + executor::{ExecState, KclValue, SourceRange}, function_param::FunctionParam, }; /// Apply a function to each element of an array. pub async fn map(exec_state: &mut ExecState, args: Args) -> Result { - let (array, f): (Vec, FnAsArg<'_>) = FromArgs::from_args(&args, 0)?; - let array: Vec = array - .into_iter() - .map(|jval| { - KclValue::UserVal(UserVal { - value: jval, - meta: vec![args.source_range.into()], - }) - }) - .collect(); + let (array, f): (Vec, FnAsArg<'_>) = FromArgs::from_args(&args, 0)?; + let meta = vec![args.source_range.into()]; let map_fn = FunctionParam { inner: f.func, fn_expr: f.expr, - meta: vec![args.source_range.into()], + meta: meta.clone(), ctx: args.ctx.clone(), memory: *f.memory, }; let new_array = inner_map(array, map_fn, exec_state, &args).await?; - Ok(KclValue::Array { - value: new_array, - meta: vec![args.source_range.into()], - }) + Ok(KclValue::Array { value: new_array, meta }) } /// Apply a function to every element of a list. @@ -100,16 +88,7 @@ async fn call_map_closure<'a>( /// For each item in an array, update a value. pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result { - let (array, start, f): (Vec, KclValue, FnAsArg<'_>) = FromArgs::from_args(&args, 0)?; - let array: Vec = array - .into_iter() - .map(|jval| { - KclValue::UserVal(UserVal { - value: jval, - meta: vec![args.source_range.into()], - }) - }) - .collect(); + let (array, start, f): (Vec, KclValue, FnAsArg<'_>) = FromArgs::from_args(&args, 0)?; let reduce_fn = FunctionParam { inner: f.func, fn_expr: f.expr, diff --git a/src/wasm-lib/kcl/src/std/assert.rs b/src/wasm-lib/kcl/src/std/assert.rs index 5a71efe73c..6f7a849237 100644 --- a/src/wasm-lib/kcl/src/std/assert.rs +++ b/src/wasm-lib/kcl/src/std/assert.rs @@ -24,7 +24,7 @@ async fn _assert(value: bool, message: &str, args: &Args) -> Result<(), KclError pub async fn assert(_exec_state: &mut ExecState, args: Args) -> Result { let (data, description): (bool, String) = args.get_data()?; inner_assert(data, &description, &args).await?; - Ok(args.make_null_user_val()) + Ok(args.make_user_val_from_f64(0.0)) // TODO: Add a new Void enum for fns that don't return anything. } /// Check a value at runtime, and raise an error if the argument provided @@ -44,7 +44,7 @@ async fn inner_assert(data: bool, message: &str, args: &Args) -> Result<(), KclE pub async fn assert_lt(_exec_state: &mut ExecState, args: Args) -> Result { let (left, right, description): (f64, f64, String) = args.get_data()?; inner_assert_lt(left, right, &description, &args).await?; - Ok(args.make_null_user_val()) + Ok(args.make_user_val_from_f64(0.0)) // TODO: Add a new Void enum for fns that don't return anything. } /// Check that a numerical value is less than to another at runtime, @@ -63,7 +63,7 @@ async fn inner_assert_lt(left: f64, right: f64, message: &str, args: &Args) -> R pub async fn assert_gt(_exec_state: &mut ExecState, args: Args) -> Result { let (left, right, description): (f64, f64, String) = args.get_data()?; inner_assert_gt(left, right, &description, &args).await?; - Ok(args.make_null_user_val()) + Ok(args.make_user_val_from_f64(0.0)) // TODO: Add a new Void enum for fns that don't return anything. } /// Check that a numerical value equals another at runtime, @@ -96,7 +96,7 @@ async fn inner_assert_equal(left: f64, right: f64, epsilon: f64, message: &str, pub async fn assert_equal(_exec_state: &mut ExecState, args: Args) -> Result { let (left, right, epsilon, description): (f64, f64, f64, String) = args.get_data()?; inner_assert_equal(left, right, epsilon, &description, &args).await?; - Ok(args.make_null_user_val()) + Ok(args.make_user_val_from_f64(0.0)) // TODO: Add a new Void enum for fns that don't return anything. } /// Check that a numerical value is greater than another at runtime, @@ -115,7 +115,7 @@ async fn inner_assert_gt(left: f64, right: f64, message: &str, args: &Args) -> R pub async fn assert_lte(_exec_state: &mut ExecState, args: Args) -> Result { let (left, right, description): (f64, f64, String) = args.get_data()?; inner_assert_lte(left, right, &description, &args).await?; - Ok(args.make_null_user_val()) + Ok(args.make_user_val_from_f64(0.0)) // TODO: Add a new Void enum for fns that don't return anything. } /// Check that a numerical value is less than or equal to another at runtime, @@ -135,7 +135,7 @@ async fn inner_assert_lte(left: f64, right: f64, message: &str, args: &Args) -> pub async fn assert_gte(_exec_state: &mut ExecState, args: Args) -> Result { let (left, right, description): (f64, f64, String) = args.get_data()?; inner_assert_gte(left, right, &description, &args).await?; - Ok(args.make_null_user_val()) + Ok(args.make_user_val_from_f64(0.0)) // TODO: Add a new Void enum for fns that don't return anything. } /// Check that a numerical value is greater than or equal to another at runtime, diff --git a/src/wasm-lib/kcl/src/std/extrude.rs b/src/wasm-lib/kcl/src/std/extrude.rs index f4e2776055..d8f2b82444 100644 --- a/src/wasm-lib/kcl/src/std/extrude.rs +++ b/src/wasm-lib/kcl/src/std/extrude.rs @@ -233,7 +233,7 @@ pub(crate) async fn do_post_extrude( tag: path.get_base().tag.clone(), geo_meta: GeoMeta { id: path.get_base().geo_meta.id, - metadata: path.get_base().geo_meta.metadata.clone(), + metadata: path.get_base().geo_meta.metadata, }, }); Some(extrude_surface) @@ -244,7 +244,7 @@ pub(crate) async fn do_post_extrude( tag: path.get_base().tag.clone(), geo_meta: GeoMeta { id: path.get_base().geo_meta.id, - metadata: path.get_base().geo_meta.metadata.clone(), + metadata: path.get_base().geo_meta.metadata, }, }); Some(extrude_surface) @@ -259,7 +259,7 @@ pub(crate) async fn do_post_extrude( tag: path.get_base().tag.clone(), geo_meta: GeoMeta { id: path.get_base().geo_meta.id, - metadata: path.get_base().geo_meta.metadata.clone(), + metadata: path.get_base().geo_meta.metadata, }, }); Some(extrude_surface) diff --git a/src/wasm-lib/kcl/src/std/fillet.rs b/src/wasm-lib/kcl/src/std/fillet.rs index 758bcbad47..029fee7181 100644 --- a/src/wasm-lib/kcl/src/std/fillet.rs +++ b/src/wasm-lib/kcl/src/std/fillet.rs @@ -14,7 +14,7 @@ use uuid::Uuid; use crate::{ ast::types::TagNode, errors::{KclError, KclErrorDetails}, - executor::{EdgeCut, ExecState, ExtrudeSurface, FilletSurface, GeoMeta, KclValue, Solid, TagIdentifier, UserVal}, + executor::{EdgeCut, ExecState, ExtrudeSurface, FilletSurface, GeoMeta, KclValue, Solid, TagIdentifier}, settings::types::UnitLength, std::Args, }; @@ -186,15 +186,10 @@ pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result let tag: TagIdentifier = args.get_data()?; let edge = inner_get_opposite_edge(tag, exec_state, args.clone()).await?; - Ok(KclValue::UserVal(UserVal { - value: serde_json::to_value(edge).map_err(|e| { - KclError::Type(KclErrorDetails { - message: format!("Failed to convert Uuid to json: {}", e), - source_ranges: vec![args.source_range], - }) - })?, + Ok(KclValue::Uuid { + value: edge, meta: vec![args.source_range.into()], - })) + }) } /// Get the opposite edge to the edge given. @@ -264,15 +259,10 @@ pub async fn get_next_adjacent_edge(exec_state: &mut ExecState, args: Args) -> R let tag: TagIdentifier = args.get_data()?; let edge = inner_get_next_adjacent_edge(tag, exec_state, args.clone()).await?; - Ok(KclValue::UserVal(UserVal { - value: serde_json::to_value(edge).map_err(|e| { - KclError::Type(KclErrorDetails { - message: format!("Failed to convert Uuid to json: {}", e), - source_ranges: vec![args.source_range], - }) - })?, + Ok(KclValue::Uuid { + value: edge, meta: vec![args.source_range.into()], - })) + }) } /// Get the next adjacent edge to the edge given. @@ -354,15 +344,10 @@ pub async fn get_previous_adjacent_edge(exec_state: &mut ExecState, args: Args) let tag: TagIdentifier = args.get_data()?; let edge = inner_get_previous_adjacent_edge(tag, exec_state, args.clone()).await?; - Ok(KclValue::UserVal(UserVal { - value: serde_json::to_value(edge).map_err(|e| { - KclError::Type(KclErrorDetails { - message: format!("Failed to convert Uuid to json: {}", e), - source_ranges: vec![args.source_range], - }) - })?, + Ok(KclValue::Uuid { + value: edge, meta: vec![args.source_range.into()], - })) + }) } /// Get the previous adjacent edge to the edge given. diff --git a/src/wasm-lib/kcl/src/std/math.rs b/src/wasm-lib/kcl/src/std/math.rs index a81c1ce835..734352e40b 100644 --- a/src/wasm-lib/kcl/src/std/math.rs +++ b/src/wasm-lib/kcl/src/std/math.rs @@ -40,7 +40,7 @@ pub async fn cos(_exec_state: &mut ExecState, args: Args) -> Result Result Result Result { pub async fn pi(_exec_state: &mut ExecState, args: Args) -> Result { let result = inner_pi()?; - args.make_user_val_from_f64(result) + Ok(args.make_user_val_from_f64(result)) } /// Return the value of `pi`. Archimedes’ constant (π). @@ -155,7 +155,7 @@ pub async fn sqrt(_exec_state: &mut ExecState, args: Args) -> Result Result Result Result Result Result Result Result Result Result Result Result Result Result Result { pub async fn e(_exec_state: &mut ExecState, args: Args) -> Result { let result = inner_e()?; - args.make_user_val_from_f64(result) + Ok(args.make_user_val_from_f64(result)) } /// Return the value of Euler’s number `e`. @@ -648,7 +648,7 @@ fn inner_e() -> Result { pub async fn tau(_exec_state: &mut ExecState, args: Args) -> Result { let result = inner_tau()?; - args.make_user_val_from_f64(result) + Ok(args.make_user_val_from_f64(result)) } /// Return the value of `tau`. The full circle constant (τ). Equal to 2π. @@ -678,7 +678,7 @@ pub async fn to_radians(_exec_state: &mut ExecState, args: Args) -> Result Result Result { let (hypotenuse, leg) = args.get_hypotenuse_leg()?; let result = inner_leg_length(hypotenuse, leg); - args.make_user_val_from_f64(result) + Ok(KclValue::from_number(result, vec![args.into()])) } /// Compute the length of the given leg. @@ -264,7 +264,7 @@ fn inner_leg_length(hypotenuse: f64, leg: f64) -> f64 { pub async fn leg_angle_x(_exec_state: &mut ExecState, args: Args) -> Result { let (hypotenuse, leg) = args.get_hypotenuse_leg()?; let result = inner_leg_angle_x(hypotenuse, leg); - args.make_user_val_from_f64(result) + Ok(KclValue::from_number(result, vec![args.into()])) } /// Compute the angle of the given leg for x. @@ -284,7 +284,7 @@ fn inner_leg_angle_x(hypotenuse: f64, leg: f64) -> f64 { pub async fn leg_angle_y(_exec_state: &mut ExecState, args: Args) -> Result { let (hypotenuse, leg) = args.get_hypotenuse_leg()?; let result = inner_leg_angle_y(hypotenuse, leg); - args.make_user_val_from_f64(result) + Ok(KclValue::from_number(result, vec![args.into()])) } /// Compute the angle of the given leg for y. diff --git a/src/wasm-lib/kcl/src/std/patterns.rs b/src/wasm-lib/kcl/src/std/patterns.rs index 25c5726715..b6fd46fb9d 100644 --- a/src/wasm-lib/kcl/src/std/patterns.rs +++ b/src/wasm-lib/kcl/src/std/patterns.rs @@ -14,13 +14,10 @@ use kittycad_modeling_cmds::{ }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_json::Value as JValue; use crate::{ errors::{KclError, KclErrorDetails}, - executor::{ - ExecState, Geometries, Geometry, KclValue, Point3d, Sketch, SketchSet, Solid, SolidSet, SourceRange, UserVal, - }, + executor::{ExecState, Geometries, Geometry, KclValue, Point3d, Sketch, SketchSet, Solid, SolidSet, SourceRange}, function_param::FunctionParam, std::{types::Uint, Args}, }; @@ -361,10 +358,10 @@ async fn make_transform<'a>( exec_state: &mut ExecState, ) -> Result { // Call the transform fn for this repetition. - let repetition_num = KclValue::UserVal(UserVal { - value: JValue::Number(i.into()), + let repetition_num = KclValue::Int { + value: i.into(), meta: vec![source_range.into()], - }); + }; let transform_fn_args = vec![repetition_num]; let transform_fn_return = transform_function.call(exec_state, transform_fn_args).await?; @@ -376,7 +373,7 @@ async fn make_transform<'a>( source_ranges: source_ranges.clone(), }) })?; - let KclValue::UserVal(transform) = transform_fn_return else { + let KclValue::Object { value: transform, meta } = transform_fn_return else { return Err(KclError::Semantic(KclErrorDetails { message: "Transform function must return a transform object".to_string(), source_ranges: source_ranges.clone(), @@ -384,9 +381,9 @@ async fn make_transform<'a>( }; // Apply defaults to the transform. - let replicate = match transform.value.get("replicate") { - Some(JValue::Bool(true)) => true, - Some(JValue::Bool(false)) => false, + let replicate = match transform.get("replicate") { + Some(KclValue::Bool { value: true, .. }) => true, + Some(KclValue::Bool { value: false, .. }) => false, Some(_) => { return Err(KclError::Semantic(KclErrorDetails { message: "The 'replicate' key must be a bool".to_string(), @@ -395,38 +392,43 @@ async fn make_transform<'a>( } None => true, }; - let scale = match transform.value.get("scale") { + let scale = match transform.get("scale") { Some(x) => array_to_point3d(x, source_ranges.clone())?, None => Point3d { x: 1.0, y: 1.0, z: 1.0 }, }; - let translate = match transform.value.get("translate") { + let translate = match transform.get("translate") { Some(x) => array_to_point3d(x, source_ranges.clone())?, None => Point3d { x: 0.0, y: 0.0, z: 0.0 }, }; let mut rotation = Rotation::default(); - if let Some(rot) = transform.value.get("rotation") { + if let Some(rot) = transform.get("rotation") { + let KclValue::Object { value: rot, meta: _ } = rot else { + return Err(KclError::Semantic(KclErrorDetails { + message: "The 'rotation' key must be an object (with optional fields 'angle', 'axis' and 'origin')" + .to_string(), + source_ranges: source_ranges.clone(), + })); + }; if let Some(axis) = rot.get("axis") { rotation.axis = array_to_point3d(axis, source_ranges.clone())?.into(); } if let Some(angle) = rot.get("angle") { match angle { - JValue::Number(number) => { - if let Some(number) = number.as_f64() { - rotation.angle = Angle::from_degrees(number); - } + KclValue::Number { value: number, meta: _ } => { + rotation.angle = Angle::from_degrees(*number); } _ => { return Err(KclError::Semantic(KclErrorDetails { message: "The 'rotation.angle' key must be a number (of degrees)".to_string(), - source_ranges: source_ranges.clone(), + source_ranges: meta.iter().map(|m| m.source_range).collect(), })); } } } if let Some(origin) = rot.get("origin") { rotation.origin = match origin { - JValue::String(s) if s == "local" => OriginType::Local, - JValue::String(s) if s == "global" => OriginType::Global, + KclValue::String { value: s, meta: _ } if s == "local" => OriginType::Local, + KclValue::String { value: s, meta: _ } if s == "global" => OriginType::Global, other => { let origin = array_to_point3d(other, source_ranges.clone())?.into(); OriginType::Custom { origin } @@ -443,8 +445,8 @@ async fn make_transform<'a>( Ok(t) } -fn array_to_point3d(json: &JValue, source_ranges: Vec) -> Result { - let JValue::Array(arr) = json else { +fn array_to_point3d(val: &KclValue, source_ranges: Vec) -> Result { + let KclValue::Array { value: arr, meta } = val else { return Err(KclError::Semantic(KclErrorDetails { message: "Expected an array of 3 numbers (i.e. a 3D point)".to_string(), source_ranges, @@ -457,17 +459,20 @@ fn array_to_point3d(json: &JValue, source_ranges: Vec) -> Result Result Result Result Result Result Result Result { pub async fn segment_length(exec_state: &mut ExecState, args: Args) -> Result { let tag: TagIdentifier = args.get_data()?; let result = inner_segment_length(&tag, exec_state, args.clone())?; - args.make_user_val_from_f64(result) + Ok(args.make_user_val_from_f64(result)) } /// Compute the length of the provided line segment. @@ -376,7 +376,7 @@ pub async fn segment_angle(exec_state: &mut ExecState, args: Args) -> Result Result { let (tag, to, sketch) = args.get_tag_to_number_sketch()?; let result = inner_angle_to_match_length_x(&tag, to, sketch, exec_state, args.clone())?; - args.make_user_val_from_f64(result) + Ok(args.make_user_val_from_f64(result)) } /// Compute the angle (in degrees) in o @@ -478,7 +478,7 @@ fn inner_angle_to_match_length_x( pub async fn angle_to_match_length_y(exec_state: &mut ExecState, args: Args) -> Result { let (tag, to, sketch) = args.get_tag_to_number_sketch()?; let result = inner_angle_to_match_length_y(&tag, to, sketch, exec_state, args.clone())?; - args.make_user_val_from_f64(result) + Ok(args.make_user_val_from_f64(result)) } /// Returns the angle to match the given length for y. diff --git a/src/wasm-lib/kcl/src/std/sketch.rs b/src/wasm-lib/kcl/src/std/sketch.rs index c2f0ada357..4f18ad33b8 100644 --- a/src/wasm-lib/kcl/src/std/sketch.rs +++ b/src/wasm-lib/kcl/src/std/sketch.rs @@ -17,7 +17,7 @@ use crate::{ errors::{KclError, KclErrorDetails}, executor::{ BasePath, ExecState, Face, GeoMeta, KclValue, Path, Plane, Point2d, Point3d, Sketch, SketchSet, SketchSurface, - Solid, TagEngineInfo, TagIdentifier, UserVal, + Solid, TagEngineInfo, TagIdentifier, }, std::{ utils::{ @@ -1262,7 +1262,7 @@ pub(crate) async fn inner_start_profile_at( pub async fn profile_start_x(_exec_state: &mut ExecState, args: Args) -> Result { let sketch: Sketch = args.get_sketch()?; let x = inner_profile_start_x(sketch)?; - args.make_user_val_from_f64(x) + Ok(args.make_user_val_from_f64(x)) } /// Extract the provided 2-dimensional sketch's profile's origin's 'x' @@ -1286,7 +1286,7 @@ pub(crate) fn inner_profile_start_x(sketch: Sketch) -> Result { pub async fn profile_start_y(_exec_state: &mut ExecState, args: Args) -> Result { let sketch: Sketch = args.get_sketch()?; let x = inner_profile_start_y(sketch)?; - args.make_user_val_from_f64(x) + Ok(args.make_user_val_from_f64(x)) } /// Extract the provided 2-dimensional sketch's profile's origin's 'y' @@ -1309,15 +1309,7 @@ pub(crate) fn inner_profile_start_y(sketch: Sketch) -> Result { pub async fn profile_start(_exec_state: &mut ExecState, args: Args) -> Result { let sketch: Sketch = args.get_sketch()?; let point = inner_profile_start(sketch)?; - Ok(KclValue::UserVal(UserVal { - value: serde_json::to_value(point).map_err(|e| { - KclError::Type(KclErrorDetails { - message: format!("Failed to convert point to json: {}", e), - source_ranges: vec![args.source_range], - }) - })?, - meta: Default::default(), - })) + Ok(KclValue::from_point2d(point, args.into())) } /// Extract the provided 2-dimensional sketch's profile's origin diff --git a/src/wasm-lib/kcl/src/std/units.rs b/src/wasm-lib/kcl/src/std/units.rs index 2e1ee77a84..404da15224 100644 --- a/src/wasm-lib/kcl/src/std/units.rs +++ b/src/wasm-lib/kcl/src/std/units.rs @@ -14,7 +14,7 @@ use crate::{ pub async fn mm(_exec_state: &mut ExecState, args: Args) -> Result { let result = inner_mm(&args)?; - args.make_user_val_from_f64(result) + Ok(args.make_user_val_from_f64(result)) } /// Millimeters conversion factor for current projects units. @@ -55,7 +55,7 @@ fn inner_mm(args: &Args) -> Result { pub async fn inch(_exec_state: &mut ExecState, args: Args) -> Result { let result = inner_inch(&args)?; - args.make_user_val_from_f64(result) + Ok(args.make_user_val_from_f64(result)) } /// Inches conversion factor for current projects units. @@ -96,7 +96,7 @@ fn inner_inch(args: &Args) -> Result { pub async fn ft(_exec_state: &mut ExecState, args: Args) -> Result { let result = inner_ft(&args)?; - args.make_user_val_from_f64(result) + Ok(args.make_user_val_from_f64(result)) } /// Feet conversion factor for current projects units. @@ -138,7 +138,7 @@ fn inner_ft(args: &Args) -> Result { pub async fn m(_exec_state: &mut ExecState, args: Args) -> Result { let result = inner_m(&args)?; - args.make_user_val_from_f64(result) + Ok(args.make_user_val_from_f64(result)) } /// Meters conversion factor for current projects units. @@ -180,7 +180,7 @@ fn inner_m(args: &Args) -> Result { pub async fn cm(_exec_state: &mut ExecState, args: Args) -> Result { let result = inner_cm(&args)?; - args.make_user_val_from_f64(result) + Ok(args.make_user_val_from_f64(result)) } /// Centimeters conversion factor for current projects units. @@ -222,7 +222,7 @@ fn inner_cm(args: &Args) -> Result { pub async fn yd(_exec_state: &mut ExecState, args: Args) -> Result { let result = inner_yd(&args)?; - args.make_user_val_from_f64(result) + Ok(args.make_user_val_from_f64(result)) } /// Yards conversion factor for current projects units.