Skip to main content

kcl_lib/execution/
kcl_value.rs

1use std::collections::HashMap;
2
3use anyhow::Result;
4use indexmap::IndexMap;
5use kittycad_modeling_cmds::units::UnitLength;
6use serde::Serialize;
7
8use crate::CompilationError;
9use crate::KclError;
10use crate::ModuleId;
11use crate::SourceRange;
12use crate::errors::KclErrorDetails;
13use crate::execution::AbstractSegment;
14use crate::execution::BoundedEdge;
15use crate::execution::EnvironmentRef;
16use crate::execution::ExecState;
17use crate::execution::Face;
18use crate::execution::GdtAnnotation;
19use crate::execution::Geometry;
20use crate::execution::GeometryWithImportedGeometry;
21use crate::execution::Helix;
22use crate::execution::ImportedGeometry;
23use crate::execution::Metadata;
24use crate::execution::Plane;
25use crate::execution::Segment;
26use crate::execution::SegmentRepr;
27use crate::execution::Sketch;
28use crate::execution::SketchConstraint;
29use crate::execution::SketchVar;
30use crate::execution::SketchVarId;
31use crate::execution::Solid;
32use crate::execution::TagIdentifier;
33use crate::execution::UnsolvedExpr;
34use crate::execution::annotations::FnAttrs;
35use crate::execution::annotations::SETTINGS;
36use crate::execution::annotations::SETTINGS_UNIT_LENGTH;
37use crate::execution::annotations::{self};
38use crate::execution::types::NumericType;
39use crate::execution::types::PrimitiveType;
40use crate::execution::types::RuntimeType;
41use crate::parsing::ast::types::DefaultParamVal;
42use crate::parsing::ast::types::FunctionExpression;
43use crate::parsing::ast::types::KclNone;
44use crate::parsing::ast::types::Literal;
45use crate::parsing::ast::types::LiteralValue;
46use crate::parsing::ast::types::Node;
47use crate::parsing::ast::types::NumericLiteral;
48use crate::parsing::ast::types::TagDeclarator;
49use crate::parsing::ast::types::TagNode;
50use crate::parsing::ast::types::Type;
51use crate::std::StdFnProps;
52use crate::std::args::TyF64;
53
54pub type KclObjectFields = HashMap<String, KclValue>;
55
56/// Any KCL value.
57#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
58#[ts(export)]
59#[serde(tag = "type")]
60pub enum KclValue {
61    Uuid {
62        value: ::uuid::Uuid,
63        #[serde(skip)]
64        meta: Vec<Metadata>,
65    },
66    Bool {
67        value: bool,
68        #[serde(skip)]
69        meta: Vec<Metadata>,
70    },
71    Number {
72        value: f64,
73        ty: NumericType,
74        #[serde(skip)]
75        meta: Vec<Metadata>,
76    },
77    String {
78        value: String,
79        #[serde(skip)]
80        meta: Vec<Metadata>,
81    },
82    SketchVar {
83        value: Box<SketchVar>,
84    },
85    SketchConstraint {
86        value: Box<SketchConstraint>,
87    },
88    Tuple {
89        value: Vec<KclValue>,
90        #[serde(skip)]
91        meta: Vec<Metadata>,
92    },
93    // An array where all values have a shared type (not necessarily the same principal type).
94    HomArray {
95        value: Vec<KclValue>,
96        // The type of values, not the array type.
97        #[serde(skip)]
98        ty: RuntimeType,
99    },
100    Object {
101        value: KclObjectFields,
102        constrainable: bool,
103        #[serde(skip)]
104        meta: Vec<Metadata>,
105    },
106    TagIdentifier(Box<TagIdentifier>),
107    TagDeclarator(crate::parsing::ast::types::BoxNode<TagDeclarator>),
108    GdtAnnotation {
109        value: Box<GdtAnnotation>,
110    },
111    Plane {
112        value: Box<Plane>,
113    },
114    Face {
115        value: Box<Face>,
116    },
117    BoundedEdge {
118        value: BoundedEdge,
119        meta: Vec<Metadata>,
120    },
121    Segment {
122        value: Box<AbstractSegment>,
123    },
124    Sketch {
125        value: Box<Sketch>,
126    },
127    Solid {
128        value: Box<Solid>,
129    },
130    Helix {
131        value: Box<Helix>,
132    },
133    ImportedGeometry(ImportedGeometry),
134    Function {
135        #[serde(serialize_with = "function_value_stub")]
136        #[ts(type = "null")]
137        value: Box<FunctionSource>,
138        #[serde(skip)]
139        meta: Vec<Metadata>,
140    },
141    Module {
142        value: ModuleId,
143        #[serde(skip)]
144        meta: Vec<Metadata>,
145    },
146    #[ts(skip)]
147    Type {
148        #[serde(skip)]
149        value: TypeDef,
150        experimental: bool,
151        #[serde(skip)]
152        meta: Vec<Metadata>,
153    },
154    KclNone {
155        value: KclNone,
156        #[serde(skip)]
157        meta: Vec<Metadata>,
158    },
159}
160
161fn function_value_stub<S>(_value: &FunctionSource, serializer: S) -> Result<S::Ok, S::Error>
162where
163    S: serde::Serializer,
164{
165    serializer.serialize_unit()
166}
167
168#[derive(Debug, Clone, PartialEq)]
169pub struct NamedParam {
170    pub experimental: bool,
171    pub default_value: Option<DefaultParamVal>,
172    pub ty: Option<Type>,
173}
174
175#[derive(Debug, Clone, PartialEq)]
176pub struct FunctionSource {
177    pub input_arg: Option<(String, Option<Type>)>,
178    pub named_args: IndexMap<String, NamedParam>,
179    pub return_type: Option<Node<Type>>,
180    pub deprecated: bool,
181    pub experimental: bool,
182    pub include_in_feature_tree: bool,
183    pub is_std: bool,
184    pub body: FunctionBody,
185    pub ast: crate::parsing::ast::types::BoxNode<FunctionExpression>,
186}
187
188pub struct KclFunctionSourceParams {
189    pub is_std: bool,
190    pub experimental: bool,
191    pub include_in_feature_tree: bool,
192}
193
194impl FunctionSource {
195    pub fn rust(
196        func: crate::std::StdFn,
197        ast: Box<Node<FunctionExpression>>,
198        _props: StdFnProps,
199        attrs: FnAttrs,
200    ) -> Self {
201        let (input_arg, named_args) = Self::args_from_ast(&ast);
202
203        FunctionSource {
204            input_arg,
205            named_args,
206            return_type: ast.return_type.clone(),
207            deprecated: attrs.deprecated,
208            experimental: attrs.experimental,
209            include_in_feature_tree: attrs.include_in_feature_tree,
210            is_std: true,
211            body: FunctionBody::Rust(func),
212            ast,
213        }
214    }
215
216    pub fn kcl(ast: Box<Node<FunctionExpression>>, memory: EnvironmentRef, params: KclFunctionSourceParams) -> Self {
217        let KclFunctionSourceParams {
218            is_std,
219            experimental,
220            include_in_feature_tree,
221        } = params;
222        let (input_arg, named_args) = Self::args_from_ast(&ast);
223        FunctionSource {
224            input_arg,
225            named_args,
226            return_type: ast.return_type.clone(),
227            deprecated: false,
228            experimental,
229            include_in_feature_tree,
230            is_std,
231            body: FunctionBody::Kcl(memory),
232            ast,
233        }
234    }
235
236    #[expect(clippy::type_complexity)]
237    fn args_from_ast(ast: &FunctionExpression) -> (Option<(String, Option<Type>)>, IndexMap<String, NamedParam>) {
238        let mut input_arg = None;
239        let mut named_args = IndexMap::new();
240        for p in &ast.params {
241            if !p.labeled {
242                input_arg = Some((
243                    p.identifier.name.clone(),
244                    p.param_type.as_ref().map(|t| t.inner.clone()),
245                ));
246                continue;
247            }
248
249            named_args.insert(
250                p.identifier.name.clone(),
251                NamedParam {
252                    experimental: p.experimental,
253                    default_value: p.default_value.clone(),
254                    ty: p.param_type.as_ref().map(|t| t.inner.clone()),
255                },
256            );
257        }
258
259        (input_arg, named_args)
260    }
261}
262
263#[derive(Debug, Clone, PartialEq)]
264// If you try to compare two `crate::std::StdFn` the results will be meaningless and arbitrary,
265// because they're just function pointers.
266#[allow(unpredictable_function_pointer_comparisons)]
267pub enum FunctionBody {
268    Rust(crate::std::StdFn),
269    Kcl(EnvironmentRef),
270}
271
272#[derive(Debug, Clone, PartialEq)]
273pub enum TypeDef {
274    RustRepr(PrimitiveType, StdFnProps),
275    Alias(RuntimeType),
276}
277
278impl From<Vec<GdtAnnotation>> for KclValue {
279    fn from(mut values: Vec<GdtAnnotation>) -> Self {
280        if values.len() == 1 {
281            let value = values.pop().expect("Just checked len == 1");
282            KclValue::GdtAnnotation { value: Box::new(value) }
283        } else {
284            KclValue::HomArray {
285                value: values
286                    .into_iter()
287                    .map(|s| KclValue::GdtAnnotation { value: Box::new(s) })
288                    .collect(),
289                ty: RuntimeType::Primitive(PrimitiveType::GdtAnnotation),
290            }
291        }
292    }
293}
294
295impl From<Vec<Sketch>> for KclValue {
296    fn from(mut eg: Vec<Sketch>) -> Self {
297        if eg.len() == 1
298            && let Some(s) = eg.pop()
299        {
300            KclValue::Sketch { value: Box::new(s) }
301        } else {
302            KclValue::HomArray {
303                value: eg
304                    .into_iter()
305                    .map(|s| KclValue::Sketch { value: Box::new(s) })
306                    .collect(),
307                ty: RuntimeType::Primitive(PrimitiveType::Sketch),
308            }
309        }
310    }
311}
312
313impl From<Vec<Solid>> for KclValue {
314    fn from(mut eg: Vec<Solid>) -> Self {
315        if eg.len() == 1
316            && let Some(s) = eg.pop()
317        {
318            KclValue::Solid { value: Box::new(s) }
319        } else {
320            KclValue::HomArray {
321                value: eg.into_iter().map(|s| KclValue::Solid { value: Box::new(s) }).collect(),
322                ty: RuntimeType::Primitive(PrimitiveType::Solid),
323            }
324        }
325    }
326}
327
328impl From<KclValue> for Vec<SourceRange> {
329    fn from(item: KclValue) -> Self {
330        match item {
331            KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
332            KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
333            KclValue::GdtAnnotation { value } => to_vec_sr(&value.meta),
334            KclValue::Solid { value } => to_vec_sr(&value.meta),
335            KclValue::Sketch { value } => to_vec_sr(&value.meta),
336            KclValue::Helix { value } => to_vec_sr(&value.meta),
337            KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
338            KclValue::Function { meta, .. } => to_vec_sr(&meta),
339            KclValue::Plane { value } => to_vec_sr(&value.meta),
340            KclValue::Face { value } => to_vec_sr(&value.meta),
341            KclValue::Segment { value } => to_vec_sr(&value.meta),
342            KclValue::Bool { meta, .. } => to_vec_sr(&meta),
343            KclValue::Number { meta, .. } => to_vec_sr(&meta),
344            KclValue::String { meta, .. } => to_vec_sr(&meta),
345            KclValue::SketchVar { value, .. } => to_vec_sr(&value.meta),
346            KclValue::SketchConstraint { value, .. } => to_vec_sr(&value.meta),
347            KclValue::Tuple { meta, .. } => to_vec_sr(&meta),
348            KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
349            KclValue::Object { meta, .. } => to_vec_sr(&meta),
350            KclValue::Module { meta, .. } => to_vec_sr(&meta),
351            KclValue::Uuid { meta, .. } => to_vec_sr(&meta),
352            KclValue::Type { meta, .. } => to_vec_sr(&meta),
353            KclValue::KclNone { meta, .. } => to_vec_sr(&meta),
354            KclValue::BoundedEdge { meta, .. } => to_vec_sr(&meta),
355        }
356    }
357}
358
359fn to_vec_sr(meta: &[Metadata]) -> Vec<SourceRange> {
360    meta.iter().map(|m| m.source_range).collect()
361}
362
363impl From<&KclValue> for Vec<SourceRange> {
364    fn from(item: &KclValue) -> Self {
365        match item {
366            KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
367            KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
368            KclValue::GdtAnnotation { value } => to_vec_sr(&value.meta),
369            KclValue::Solid { value } => to_vec_sr(&value.meta),
370            KclValue::Sketch { value } => to_vec_sr(&value.meta),
371            KclValue::Helix { value } => to_vec_sr(&value.meta),
372            KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
373            KclValue::Function { meta, .. } => to_vec_sr(meta),
374            KclValue::Plane { value } => to_vec_sr(&value.meta),
375            KclValue::Face { value } => to_vec_sr(&value.meta),
376            KclValue::Segment { value } => to_vec_sr(&value.meta),
377            KclValue::Bool { meta, .. } => to_vec_sr(meta),
378            KclValue::Number { meta, .. } => to_vec_sr(meta),
379            KclValue::String { meta, .. } => to_vec_sr(meta),
380            KclValue::SketchVar { value, .. } => to_vec_sr(&value.meta),
381            KclValue::SketchConstraint { value, .. } => to_vec_sr(&value.meta),
382            KclValue::Uuid { meta, .. } => to_vec_sr(meta),
383            KclValue::Tuple { meta, .. } => to_vec_sr(meta),
384            KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
385            KclValue::Object { meta, .. } => to_vec_sr(meta),
386            KclValue::Module { meta, .. } => to_vec_sr(meta),
387            KclValue::KclNone { meta, .. } => to_vec_sr(meta),
388            KclValue::Type { meta, .. } => to_vec_sr(meta),
389            KclValue::BoundedEdge { meta, .. } => to_vec_sr(meta),
390        }
391    }
392}
393
394impl From<&KclValue> for SourceRange {
395    fn from(item: &KclValue) -> Self {
396        let v: Vec<_> = item.into();
397        v.into_iter().next().unwrap_or_default()
398    }
399}
400
401impl KclValue {
402    pub(crate) fn metadata(&self) -> Vec<Metadata> {
403        match self {
404            KclValue::Uuid { value: _, meta } => meta.clone(),
405            KclValue::Bool { value: _, meta } => meta.clone(),
406            KclValue::Number { meta, .. } => meta.clone(),
407            KclValue::String { value: _, meta } => meta.clone(),
408            KclValue::SketchVar { value, .. } => value.meta.clone(),
409            KclValue::SketchConstraint { value, .. } => value.meta.clone(),
410            KclValue::Tuple { value: _, meta } => meta.clone(),
411            KclValue::HomArray { value, .. } => value.iter().flat_map(|v| v.metadata()).collect(),
412            KclValue::Object { meta, .. } => meta.clone(),
413            KclValue::TagIdentifier(x) => x.meta.clone(),
414            KclValue::TagDeclarator(x) => vec![x.metadata()],
415            KclValue::GdtAnnotation { value } => value.meta.clone(),
416            KclValue::Plane { value } => value.meta.clone(),
417            KclValue::Face { value } => value.meta.clone(),
418            KclValue::Segment { value } => value.meta.clone(),
419            KclValue::Sketch { value } => value.meta.clone(),
420            KclValue::Solid { value } => value.meta.clone(),
421            KclValue::Helix { value } => value.meta.clone(),
422            KclValue::ImportedGeometry(x) => x.meta.clone(),
423            KclValue::Function { meta, .. } => meta.clone(),
424            KclValue::Module { meta, .. } => meta.clone(),
425            KclValue::KclNone { meta, .. } => meta.clone(),
426            KclValue::Type { meta, .. } => meta.clone(),
427            KclValue::BoundedEdge { meta, .. } => meta.clone(),
428        }
429    }
430
431    #[allow(unused)]
432    pub(crate) fn none() -> Self {
433        Self::KclNone {
434            value: Default::default(),
435            meta: Default::default(),
436        }
437    }
438
439    /// Returns true if we should generate an [`crate::execution::Operation`] to
440    /// display in the Feature Tree for variable declarations initialized with
441    /// this value.
442    pub(crate) fn show_variable_in_feature_tree(&self) -> bool {
443        match self {
444            KclValue::Uuid { .. } => false,
445            KclValue::Bool { .. } | KclValue::Number { .. } | KclValue::String { .. } => true,
446            KclValue::SketchVar { .. }
447            | KclValue::SketchConstraint { .. }
448            | KclValue::Tuple { .. }
449            | KclValue::HomArray { .. }
450            | KclValue::Object { .. }
451            | KclValue::TagIdentifier(_)
452            | KclValue::TagDeclarator(_)
453            | KclValue::GdtAnnotation { .. }
454            | KclValue::Plane { .. }
455            | KclValue::Face { .. }
456            | KclValue::Segment { .. }
457            | KclValue::Sketch { .. }
458            | KclValue::Solid { .. }
459            | KclValue::Helix { .. }
460            | KclValue::ImportedGeometry(_)
461            | KclValue::Function { .. }
462            | KclValue::Module { .. }
463            | KclValue::Type { .. }
464            | KclValue::BoundedEdge { .. }
465            | KclValue::KclNone { .. } => false,
466        }
467    }
468
469    /// Human readable type name used in error messages.  Should not be relied
470    /// on for program logic.
471    pub(crate) fn human_friendly_type(&self) -> String {
472        match self {
473            KclValue::Uuid { .. } => "a unique ID (uuid)".to_owned(),
474            KclValue::TagDeclarator(_) => "a tag declarator".to_owned(),
475            KclValue::TagIdentifier(_) => "a tag identifier".to_owned(),
476            KclValue::GdtAnnotation { .. } => "an annotation".to_owned(),
477            KclValue::Solid { .. } => "a solid".to_owned(),
478            KclValue::Sketch { .. } => "a sketch".to_owned(),
479            KclValue::Helix { .. } => "a helix".to_owned(),
480            KclValue::ImportedGeometry(_) => "an imported geometry".to_owned(),
481            KclValue::Function { .. } => "a function".to_owned(),
482            KclValue::Plane { .. } => "a plane".to_owned(),
483            KclValue::Face { .. } => "a face".to_owned(),
484            KclValue::Segment { .. } => "a segment".to_owned(),
485            KclValue::Bool { .. } => "a boolean (`true` or `false`)".to_owned(),
486            KclValue::Number {
487                ty: NumericType::Unknown,
488                ..
489            } => "a number with unknown units".to_owned(),
490            KclValue::Number {
491                ty: NumericType::Known(units),
492                ..
493            } => format!("a number ({units})"),
494            KclValue::Number { .. } => "a number".to_owned(),
495            KclValue::String { .. } => "a string".to_owned(),
496            KclValue::SketchVar { .. } => "a sketch variable".to_owned(),
497            KclValue::SketchConstraint { .. } => "a sketch constraint".to_owned(),
498            KclValue::Object { .. } => "an object".to_owned(),
499            KclValue::Module { .. } => "a module".to_owned(),
500            KclValue::Type { .. } => "a type".to_owned(),
501            KclValue::KclNone { .. } => "none".to_owned(),
502            KclValue::BoundedEdge { .. } => "a bounded edge".to_owned(),
503            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
504                if value.is_empty() {
505                    "an empty array".to_owned()
506                } else {
507                    // A max of 3 is good because it's common to use 3D points.
508                    const MAX: usize = 3;
509
510                    let len = value.len();
511                    let element_tys = value
512                        .iter()
513                        .take(MAX)
514                        .map(|elem| elem.principal_type_string())
515                        .collect::<Vec<_>>()
516                        .join(", ");
517                    let mut result = format!("an array of {element_tys}");
518                    if len > MAX {
519                        result.push_str(&format!(", ... with {len} values"));
520                    }
521                    if len == 1 {
522                        result.push_str(" with 1 value");
523                    }
524                    result
525                }
526            }
527        }
528    }
529
530    pub(crate) fn from_sketch_var_literal(
531        literal: &Node<NumericLiteral>,
532        id: SketchVarId,
533        exec_state: &ExecState,
534    ) -> Self {
535        let meta = vec![literal.metadata()];
536        let ty = NumericType::from_parsed(literal.suffix, &exec_state.mod_local.settings);
537        KclValue::SketchVar {
538            value: Box::new(SketchVar {
539                id,
540                initial_value: literal.value,
541                meta,
542                ty,
543            }),
544        }
545    }
546
547    pub(crate) fn from_literal(literal: Node<Literal>, exec_state: &mut ExecState) -> Self {
548        let meta = vec![literal.metadata()];
549        match literal.inner.value {
550            LiteralValue::Number { value, suffix } => {
551                let ty = NumericType::from_parsed(suffix, &exec_state.mod_local.settings);
552                if let NumericType::Default { len, .. } = &ty
553                    && !exec_state.mod_local.explicit_length_units
554                    && *len != UnitLength::Millimeters
555                {
556                    exec_state.warn(
557                        CompilationError::err(
558                            literal.as_source_range(),
559                            "Project-wide units are deprecated. Prefer to use per-file default units.",
560                        )
561                        .with_suggestion(
562                            "Fix by adding per-file settings",
563                            format!("@{SETTINGS}({SETTINGS_UNIT_LENGTH} = {len})\n"),
564                            // Insert at the start of the file.
565                            Some(SourceRange::new(0, 0, literal.module_id)),
566                            crate::errors::Tag::Deprecated,
567                        ),
568                        annotations::WARN_DEPRECATED,
569                    );
570                }
571                KclValue::Number { value, meta, ty }
572            }
573            LiteralValue::String(value) => KclValue::String { value, meta },
574            LiteralValue::Bool(value) => KclValue::Bool { value, meta },
575        }
576    }
577
578    pub(crate) fn from_default_param(param: DefaultParamVal, exec_state: &mut ExecState) -> Self {
579        match param {
580            DefaultParamVal::Literal(lit) => Self::from_literal(lit, exec_state),
581            DefaultParamVal::KclNone(value) => KclValue::KclNone {
582                value,
583                meta: Default::default(),
584            },
585        }
586    }
587
588    pub(crate) fn map_env_ref(&self, old_env: EnvironmentRef, new_env: EnvironmentRef) -> Self {
589        let mut result = self.clone();
590        if let KclValue::Function { ref mut value, .. } = result
591            && let FunctionSource {
592                body: FunctionBody::Kcl(memory),
593                ..
594            } = &mut **value
595        {
596            memory.replace_env(old_env, new_env);
597        }
598
599        result
600    }
601
602    pub(crate) fn map_env_ref_and_epoch(&self, old_env: EnvironmentRef, new_env: EnvironmentRef) -> Self {
603        let mut result = self.clone();
604        if let KclValue::Function { ref mut value, .. } = result
605            && let FunctionSource {
606                body: FunctionBody::Kcl(memory),
607                ..
608            } = &mut **value
609        {
610            memory.replace_env_and_epoch(old_env, new_env);
611        }
612
613        result
614    }
615
616    pub const fn from_number_with_type(f: f64, ty: NumericType, meta: Vec<Metadata>) -> Self {
617        Self::Number { value: f, meta, ty }
618    }
619
620    /// Put the point into a KCL value.
621    pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
622        let [x, y] = p;
623        Self::Tuple {
624            value: vec![
625                Self::Number {
626                    value: x,
627                    meta: meta.clone(),
628                    ty,
629                },
630                Self::Number {
631                    value: y,
632                    meta: meta.clone(),
633                    ty,
634                },
635            ],
636            meta,
637        }
638    }
639
640    /// Put the point into a KCL value.
641    pub fn from_point3d(p: [f64; 3], ty: NumericType, meta: Vec<Metadata>) -> Self {
642        let [x, y, z] = p;
643        Self::Tuple {
644            value: vec![
645                Self::Number {
646                    value: x,
647                    meta: meta.clone(),
648                    ty,
649                },
650                Self::Number {
651                    value: y,
652                    meta: meta.clone(),
653                    ty,
654                },
655                Self::Number {
656                    value: z,
657                    meta: meta.clone(),
658                    ty,
659                },
660            ],
661            meta,
662        }
663    }
664
665    /// Put the point into a KCL point.
666    pub(crate) fn array_from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
667        let [x, y] = p;
668        Self::HomArray {
669            value: vec![
670                Self::Number {
671                    value: x,
672                    meta: meta.clone(),
673                    ty,
674                },
675                Self::Number { value: y, meta, ty },
676            ],
677            ty: ty.into(),
678        }
679    }
680
681    /// Put the point into a KCL point.
682    pub fn array_from_point3d(p: [f64; 3], ty: NumericType, meta: Vec<Metadata>) -> Self {
683        let [x, y, z] = p;
684        Self::HomArray {
685            value: vec![
686                Self::Number {
687                    value: x,
688                    meta: meta.clone(),
689                    ty,
690                },
691                Self::Number {
692                    value: y,
693                    meta: meta.clone(),
694                    ty,
695                },
696                Self::Number { value: z, meta, ty },
697            ],
698            ty: ty.into(),
699        }
700    }
701
702    pub(crate) fn from_unsolved_expr(expr: UnsolvedExpr, meta: Vec<Metadata>) -> Self {
703        match expr {
704            UnsolvedExpr::Known(v) => crate::execution::KclValue::Number {
705                value: v.n,
706                ty: v.ty,
707                meta,
708            },
709            UnsolvedExpr::Unknown(var_id) => crate::execution::KclValue::SketchVar {
710                value: Box::new(SketchVar {
711                    id: var_id,
712                    initial_value: Default::default(),
713                    // TODO: Should this be the solver units?
714                    ty: Default::default(),
715                    meta,
716                }),
717            },
718        }
719    }
720
721    pub(crate) fn as_usize(&self) -> Option<usize> {
722        match self {
723            KclValue::Number { value, .. } => crate::try_f64_to_usize(*value),
724            _ => None,
725        }
726    }
727
728    pub fn as_int(&self) -> Option<i64> {
729        match self {
730            KclValue::Number { value, .. } => crate::try_f64_to_i64(*value),
731            _ => None,
732        }
733    }
734
735    pub fn as_int_with_ty(&self) -> Option<(i64, NumericType)> {
736        match self {
737            KclValue::Number { value, ty, .. } => crate::try_f64_to_i64(*value).map(|i| (i, *ty)),
738            _ => None,
739        }
740    }
741
742    pub fn as_object(&self) -> Option<&KclObjectFields> {
743        match self {
744            KclValue::Object { value, .. } => Some(value),
745            _ => None,
746        }
747    }
748
749    pub fn into_object(self) -> Option<KclObjectFields> {
750        match self {
751            KclValue::Object { value, .. } => Some(value),
752            _ => None,
753        }
754    }
755
756    pub fn as_unsolved_expr(&self) -> Option<UnsolvedExpr> {
757        match self {
758            KclValue::Number { value, ty, .. } => Some(UnsolvedExpr::Known(TyF64::new(*value, *ty))),
759            KclValue::SketchVar { value, .. } => Some(UnsolvedExpr::Unknown(value.id)),
760            _ => None,
761        }
762    }
763
764    pub fn to_sketch_expr(&self) -> Option<crate::front::Expr> {
765        match self {
766            KclValue::Number { value, ty, .. } => Some(crate::front::Expr::Number(crate::front::Number {
767                value: *value,
768                units: (*ty).try_into().ok()?,
769            })),
770            KclValue::SketchVar { value, .. } => Some(crate::front::Expr::Var(crate::front::Number {
771                value: value.initial_value,
772                units: value.ty.try_into().ok()?,
773            })),
774            _ => None,
775        }
776    }
777
778    pub fn as_str(&self) -> Option<&str> {
779        match self {
780            KclValue::String { value, .. } => Some(value),
781            _ => None,
782        }
783    }
784
785    pub fn into_array(self) -> Vec<KclValue> {
786        match self {
787            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
788            _ => vec![self],
789        }
790    }
791
792    pub fn as_point2d(&self) -> Option<[TyF64; 2]> {
793        let value = match self {
794            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
795            _ => return None,
796        };
797
798        let [x, y] = value.as_slice() else {
799            return None;
800        };
801        let x = x.as_ty_f64()?;
802        let y = y.as_ty_f64()?;
803        Some([x, y])
804    }
805
806    pub fn as_point3d(&self) -> Option<[TyF64; 3]> {
807        let value = match self {
808            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
809            _ => return None,
810        };
811
812        let [x, y, z] = value.as_slice() else {
813            return None;
814        };
815        let x = x.as_ty_f64()?;
816        let y = y.as_ty_f64()?;
817        let z = z.as_ty_f64()?;
818        Some([x, y, z])
819    }
820
821    pub fn as_uuid(&self) -> Option<uuid::Uuid> {
822        match self {
823            KclValue::Uuid { value, .. } => Some(*value),
824            _ => None,
825        }
826    }
827
828    pub fn as_plane(&self) -> Option<&Plane> {
829        match self {
830            KclValue::Plane { value, .. } => Some(value),
831            _ => None,
832        }
833    }
834
835    pub fn as_solid(&self) -> Option<&Solid> {
836        match self {
837            KclValue::Solid { value, .. } => Some(value),
838            _ => None,
839        }
840    }
841
842    pub fn as_sketch(&self) -> Option<&Sketch> {
843        match self {
844            KclValue::Sketch { value, .. } => Some(value),
845            _ => None,
846        }
847    }
848
849    pub fn as_mut_sketch(&mut self) -> Option<&mut Sketch> {
850        match self {
851            KclValue::Sketch { value } => Some(value),
852            _ => None,
853        }
854    }
855
856    pub fn as_sketch_var(&self) -> Option<&SketchVar> {
857        match self {
858            KclValue::SketchVar { value, .. } => Some(value),
859            _ => None,
860        }
861    }
862
863    /// A solved segment.
864    pub fn as_segment(&self) -> Option<&Segment> {
865        match self {
866            KclValue::Segment { value, .. } => match &value.repr {
867                SegmentRepr::Solved { segment } => Some(segment),
868                _ => None,
869            },
870            _ => None,
871        }
872    }
873
874    /// A solved segment.
875    pub fn into_segment(self) -> Option<Segment> {
876        match self {
877            KclValue::Segment { value, .. } => match value.repr {
878                SegmentRepr::Solved { segment } => Some(*segment),
879                _ => None,
880            },
881            _ => None,
882        }
883    }
884
885    pub fn as_mut_tag(&mut self) -> Option<&mut TagIdentifier> {
886        match self {
887            KclValue::TagIdentifier(value) => Some(value),
888            _ => None,
889        }
890    }
891
892    #[cfg(test)]
893    pub fn as_f64(&self) -> Option<f64> {
894        match self {
895            KclValue::Number { value, .. } => Some(*value),
896            _ => None,
897        }
898    }
899
900    pub fn as_ty_f64(&self) -> Option<TyF64> {
901        match self {
902            KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, *ty)),
903            _ => None,
904        }
905    }
906
907    pub fn as_bool(&self) -> Option<bool> {
908        match self {
909            KclValue::Bool { value, .. } => Some(*value),
910            _ => None,
911        }
912    }
913
914    /// If this value is of type function, return it.
915    pub fn as_function(&self) -> Option<&FunctionSource> {
916        match self {
917            KclValue::Function { value, .. } => Some(value),
918            _ => None,
919        }
920    }
921
922    /// Get a tag identifier from a memory item.
923    pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> {
924        match self {
925            KclValue::TagIdentifier(t) => Ok(*t.clone()),
926            _ => Err(KclError::new_semantic(KclErrorDetails::new(
927                format!("Not a tag identifier: {self:?}"),
928                self.clone().into(),
929            ))),
930        }
931    }
932
933    /// Get a tag declarator from a memory item.
934    pub fn get_tag_declarator(&self) -> Result<TagNode, KclError> {
935        match self {
936            KclValue::TagDeclarator(t) => Ok((**t).clone()),
937            _ => Err(KclError::new_semantic(KclErrorDetails::new(
938                format!("Not a tag declarator: {self:?}"),
939                self.clone().into(),
940            ))),
941        }
942    }
943
944    /// If this KCL value is a bool, retrieve it.
945    pub fn get_bool(&self) -> Result<bool, KclError> {
946        self.as_bool().ok_or_else(|| {
947            KclError::new_type(KclErrorDetails::new(
948                format!("Expected bool, found {}", self.human_friendly_type()),
949                self.into(),
950            ))
951        })
952    }
953
954    pub fn is_unknown_number(&self) -> bool {
955        match self {
956            KclValue::Number { ty, .. } => !ty.is_fully_specified(),
957            _ => false,
958        }
959    }
960
961    pub fn value_str(&self) -> Option<String> {
962        match self {
963            KclValue::Bool { value, .. } => Some(format!("{value}")),
964            // TODO: Show units.
965            KclValue::Number { value, .. } => Some(format!("{value}")),
966            KclValue::String { value, .. } => Some(format!("'{value}'")),
967            // TODO: Show units.
968            KclValue::SketchVar { value, .. } => Some(format!("var {}", value.initial_value)),
969            KclValue::Uuid { value, .. } => Some(format!("{value}")),
970            KclValue::TagDeclarator(tag) => Some(format!("${}", tag.name)),
971            KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)),
972            // TODO better Array and Object stringification
973            KclValue::Tuple { .. } => Some("[...]".to_owned()),
974            KclValue::HomArray { .. } => Some("[...]".to_owned()),
975            KclValue::Object { .. } => Some("{ ... }".to_owned()),
976            KclValue::Module { .. }
977            | KclValue::GdtAnnotation { .. }
978            | KclValue::SketchConstraint { .. }
979            | KclValue::Solid { .. }
980            | KclValue::Sketch { .. }
981            | KclValue::Helix { .. }
982            | KclValue::ImportedGeometry(_)
983            | KclValue::Function { .. }
984            | KclValue::Plane { .. }
985            | KclValue::Face { .. }
986            | KclValue::Segment { .. }
987            | KclValue::KclNone { .. }
988            | KclValue::BoundedEdge { .. }
989            | KclValue::Type { .. } => None,
990        }
991    }
992}
993
994impl From<Geometry> for KclValue {
995    fn from(value: Geometry) -> Self {
996        match value {
997            Geometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
998            Geometry::Solid(x) => Self::Solid { value: Box::new(x) },
999        }
1000    }
1001}
1002
1003impl From<GeometryWithImportedGeometry> for KclValue {
1004    fn from(value: GeometryWithImportedGeometry) -> Self {
1005        match value {
1006            GeometryWithImportedGeometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
1007            GeometryWithImportedGeometry::Solid(x) => Self::Solid { value: Box::new(x) },
1008            GeometryWithImportedGeometry::ImportedGeometry(x) => Self::ImportedGeometry(*x),
1009        }
1010    }
1011}
1012
1013impl From<Vec<GeometryWithImportedGeometry>> for KclValue {
1014    fn from(mut values: Vec<GeometryWithImportedGeometry>) -> Self {
1015        if values.len() == 1
1016            && let Some(v) = values.pop()
1017        {
1018            KclValue::from(v)
1019        } else {
1020            KclValue::HomArray {
1021                value: values.into_iter().map(KclValue::from).collect(),
1022                ty: RuntimeType::Union(vec![
1023                    RuntimeType::Primitive(PrimitiveType::Sketch),
1024                    RuntimeType::Primitive(PrimitiveType::Solid),
1025                    RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
1026                ]),
1027            }
1028        }
1029    }
1030}
1031
1032#[cfg(test)]
1033mod tests {
1034    use super::*;
1035    use crate::exec::UnitType;
1036
1037    #[test]
1038    fn test_human_friendly_type() {
1039        let len = KclValue::Number {
1040            value: 1.0,
1041            ty: NumericType::Known(UnitType::GenericLength),
1042            meta: vec![],
1043        };
1044        assert_eq!(len.human_friendly_type(), "a number (Length)".to_string());
1045
1046        let unknown = KclValue::Number {
1047            value: 1.0,
1048            ty: NumericType::Unknown,
1049            meta: vec![],
1050        };
1051        assert_eq!(unknown.human_friendly_type(), "a number with unknown units".to_string());
1052
1053        let mm = KclValue::Number {
1054            value: 1.0,
1055            ty: NumericType::Known(UnitType::Length(UnitLength::Millimeters)),
1056            meta: vec![],
1057        };
1058        assert_eq!(mm.human_friendly_type(), "a number (mm)".to_string());
1059
1060        let array1_mm = KclValue::HomArray {
1061            value: vec![mm.clone()],
1062            ty: RuntimeType::any(),
1063        };
1064        assert_eq!(
1065            array1_mm.human_friendly_type(),
1066            "an array of `number(mm)` with 1 value".to_string()
1067        );
1068
1069        let array2_mm = KclValue::HomArray {
1070            value: vec![mm.clone(), mm.clone()],
1071            ty: RuntimeType::any(),
1072        };
1073        assert_eq!(
1074            array2_mm.human_friendly_type(),
1075            "an array of `number(mm)`, `number(mm)`".to_string()
1076        );
1077
1078        let array3_mm = KclValue::HomArray {
1079            value: vec![mm.clone(), mm.clone(), mm.clone()],
1080            ty: RuntimeType::any(),
1081        };
1082        assert_eq!(
1083            array3_mm.human_friendly_type(),
1084            "an array of `number(mm)`, `number(mm)`, `number(mm)`".to_string()
1085        );
1086
1087        let inches = KclValue::Number {
1088            value: 1.0,
1089            ty: NumericType::Known(UnitType::Length(UnitLength::Inches)),
1090            meta: vec![],
1091        };
1092        let array4 = KclValue::HomArray {
1093            value: vec![mm.clone(), mm.clone(), inches, mm],
1094            ty: RuntimeType::any(),
1095        };
1096        assert_eq!(
1097            array4.human_friendly_type(),
1098            "an array of `number(mm)`, `number(mm)`, `number(in)`, ... with 4 values".to_string()
1099        );
1100
1101        let empty_array = KclValue::HomArray {
1102            value: vec![],
1103            ty: RuntimeType::any(),
1104        };
1105        assert_eq!(empty_array.human_friendly_type(), "an empty array".to_string());
1106
1107        let array_nested = KclValue::HomArray {
1108            value: vec![array2_mm],
1109            ty: RuntimeType::any(),
1110        };
1111        assert_eq!(
1112            array_nested.human_friendly_type(),
1113            "an array of `[any; 2]` with 1 value".to_string()
1114        );
1115    }
1116}