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