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