kcl_lib/execution/
kcl_value.rs

1use std::collections::HashMap;
2
3use anyhow::Result;
4use schemars::JsonSchema;
5use serde::Serialize;
6
7use super::types::UnitType;
8use crate::{
9    errors::KclErrorDetails,
10    execution::{
11        annotations::{SETTINGS, SETTINGS_UNIT_LENGTH},
12        types::{NumericType, PrimitiveType, RuntimeType, UnitLen},
13        EnvironmentRef, ExecState, Face, Geometry, GeometryWithImportedGeometry, Helix, ImportedGeometry, MetaSettings,
14        Metadata, Plane, Sketch, Solid, TagIdentifier,
15    },
16    parsing::ast::types::{
17        DefaultParamVal, FunctionExpression, KclNone, Literal, LiteralValue, Node, TagDeclarator, TagNode,
18    },
19    std::{args::TyF64, StdFnProps},
20    CompilationError, KclError, ModuleId, SourceRange,
21};
22
23pub type KclObjectFields = HashMap<String, KclValue>;
24
25/// Any KCL value.
26#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
27#[ts(export)]
28#[serde(tag = "type")]
29pub enum KclValue {
30    Uuid {
31        value: ::uuid::Uuid,
32        #[serde(skip)]
33        meta: Vec<Metadata>,
34    },
35    Bool {
36        value: bool,
37        #[serde(skip)]
38        meta: Vec<Metadata>,
39    },
40    Number {
41        value: f64,
42        ty: NumericType,
43        #[serde(skip)]
44        meta: Vec<Metadata>,
45    },
46    String {
47        value: String,
48        #[serde(skip)]
49        meta: Vec<Metadata>,
50    },
51    Tuple {
52        value: Vec<KclValue>,
53        #[serde(skip)]
54        meta: Vec<Metadata>,
55    },
56    // An array where all values have a shared type (not necessarily the same principal type).
57    HomArray {
58        value: Vec<KclValue>,
59        // The type of values, not the array type.
60        #[serde(skip)]
61        ty: RuntimeType,
62    },
63    Object {
64        value: KclObjectFields,
65        #[serde(skip)]
66        meta: Vec<Metadata>,
67    },
68    TagIdentifier(Box<TagIdentifier>),
69    TagDeclarator(crate::parsing::ast::types::BoxNode<TagDeclarator>),
70    Plane {
71        value: Box<Plane>,
72    },
73    Face {
74        value: Box<Face>,
75    },
76    Sketch {
77        value: Box<Sketch>,
78    },
79    Solid {
80        value: Box<Solid>,
81    },
82    Helix {
83        value: Box<Helix>,
84    },
85    ImportedGeometry(ImportedGeometry),
86    Function {
87        #[serde(serialize_with = "function_value_stub")]
88        #[ts(type = "null")]
89        value: FunctionSource,
90        #[serde(skip)]
91        meta: Vec<Metadata>,
92    },
93    Module {
94        value: ModuleId,
95        #[serde(skip)]
96        meta: Vec<Metadata>,
97    },
98    #[ts(skip)]
99    Type {
100        #[serde(skip)]
101        value: TypeDef,
102        #[serde(skip)]
103        meta: Vec<Metadata>,
104    },
105    KclNone {
106        value: KclNone,
107        #[serde(skip)]
108        meta: Vec<Metadata>,
109    },
110}
111
112fn function_value_stub<S>(_value: &FunctionSource, serializer: S) -> Result<S::Ok, S::Error>
113where
114    S: serde::Serializer,
115{
116    serializer.serialize_unit()
117}
118
119#[derive(Debug, Clone, PartialEq, Default)]
120pub enum FunctionSource {
121    #[default]
122    None,
123    Std {
124        func: crate::std::StdFn,
125        ast: crate::parsing::ast::types::BoxNode<FunctionExpression>,
126        props: StdFnProps,
127    },
128    User {
129        ast: crate::parsing::ast::types::BoxNode<FunctionExpression>,
130        settings: MetaSettings,
131        memory: EnvironmentRef,
132    },
133}
134
135impl JsonSchema for FunctionSource {
136    fn schema_name() -> String {
137        "FunctionSource".to_owned()
138    }
139
140    fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
141        // TODO: Actually generate a reasonable schema.
142        gen.subschema_for::<()>()
143    }
144}
145
146#[derive(Debug, Clone, PartialEq)]
147pub enum TypeDef {
148    RustRepr(PrimitiveType, StdFnProps),
149    Alias(RuntimeType),
150}
151
152impl From<Vec<Sketch>> for KclValue {
153    fn from(mut eg: Vec<Sketch>) -> Self {
154        if eg.len() == 1 {
155            KclValue::Sketch {
156                value: Box::new(eg.pop().unwrap()),
157            }
158        } else {
159            KclValue::HomArray {
160                value: eg
161                    .into_iter()
162                    .map(|s| KclValue::Sketch { value: Box::new(s) })
163                    .collect(),
164                ty: RuntimeType::Primitive(PrimitiveType::Sketch),
165            }
166        }
167    }
168}
169
170impl From<Vec<Solid>> for KclValue {
171    fn from(mut eg: Vec<Solid>) -> Self {
172        if eg.len() == 1 {
173            KclValue::Solid {
174                value: Box::new(eg.pop().unwrap()),
175            }
176        } else {
177            KclValue::HomArray {
178                value: eg.into_iter().map(|s| KclValue::Solid { value: Box::new(s) }).collect(),
179                ty: RuntimeType::Primitive(PrimitiveType::Solid),
180            }
181        }
182    }
183}
184
185impl From<KclValue> for Vec<SourceRange> {
186    fn from(item: KclValue) -> Self {
187        match item {
188            KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
189            KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
190            KclValue::Solid { value } => to_vec_sr(&value.meta),
191            KclValue::Sketch { value } => to_vec_sr(&value.meta),
192            KclValue::Helix { value } => to_vec_sr(&value.meta),
193            KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
194            KclValue::Function { meta, .. } => to_vec_sr(&meta),
195            KclValue::Plane { value } => to_vec_sr(&value.meta),
196            KclValue::Face { value } => to_vec_sr(&value.meta),
197            KclValue::Bool { meta, .. } => to_vec_sr(&meta),
198            KclValue::Number { meta, .. } => to_vec_sr(&meta),
199            KclValue::String { meta, .. } => to_vec_sr(&meta),
200            KclValue::Tuple { meta, .. } => to_vec_sr(&meta),
201            KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
202            KclValue::Object { meta, .. } => to_vec_sr(&meta),
203            KclValue::Module { meta, .. } => to_vec_sr(&meta),
204            KclValue::Uuid { meta, .. } => to_vec_sr(&meta),
205            KclValue::Type { meta, .. } => to_vec_sr(&meta),
206            KclValue::KclNone { meta, .. } => to_vec_sr(&meta),
207        }
208    }
209}
210
211fn to_vec_sr(meta: &[Metadata]) -> Vec<SourceRange> {
212    meta.iter().map(|m| m.source_range).collect()
213}
214
215impl From<&KclValue> for Vec<SourceRange> {
216    fn from(item: &KclValue) -> Self {
217        match item {
218            KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
219            KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
220            KclValue::Solid { value } => to_vec_sr(&value.meta),
221            KclValue::Sketch { value } => to_vec_sr(&value.meta),
222            KclValue::Helix { value } => to_vec_sr(&value.meta),
223            KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
224            KclValue::Function { meta, .. } => to_vec_sr(meta),
225            KclValue::Plane { value } => to_vec_sr(&value.meta),
226            KclValue::Face { value } => to_vec_sr(&value.meta),
227            KclValue::Bool { meta, .. } => to_vec_sr(meta),
228            KclValue::Number { meta, .. } => to_vec_sr(meta),
229            KclValue::String { meta, .. } => to_vec_sr(meta),
230            KclValue::Uuid { meta, .. } => to_vec_sr(meta),
231            KclValue::Tuple { meta, .. } => to_vec_sr(meta),
232            KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
233            KclValue::Object { meta, .. } => to_vec_sr(meta),
234            KclValue::Module { meta, .. } => to_vec_sr(meta),
235            KclValue::KclNone { meta, .. } => to_vec_sr(meta),
236            KclValue::Type { meta, .. } => to_vec_sr(meta),
237        }
238    }
239}
240
241impl From<&KclValue> for SourceRange {
242    fn from(item: &KclValue) -> Self {
243        let v: Vec<_> = item.into();
244        v.into_iter().next().unwrap_or_default()
245    }
246}
247
248impl KclValue {
249    pub(crate) fn metadata(&self) -> Vec<Metadata> {
250        match self {
251            KclValue::Uuid { value: _, meta } => meta.clone(),
252            KclValue::Bool { value: _, meta } => meta.clone(),
253            KclValue::Number { meta, .. } => meta.clone(),
254            KclValue::String { value: _, meta } => meta.clone(),
255            KclValue::Tuple { value: _, meta } => meta.clone(),
256            KclValue::HomArray { value, .. } => value.iter().flat_map(|v| v.metadata()).collect(),
257            KclValue::Object { value: _, meta } => meta.clone(),
258            KclValue::TagIdentifier(x) => x.meta.clone(),
259            KclValue::TagDeclarator(x) => vec![x.metadata()],
260            KclValue::Plane { value } => value.meta.clone(),
261            KclValue::Face { value } => value.meta.clone(),
262            KclValue::Sketch { value } => value.meta.clone(),
263            KclValue::Solid { value } => value.meta.clone(),
264            KclValue::Helix { value } => value.meta.clone(),
265            KclValue::ImportedGeometry(x) => x.meta.clone(),
266            KclValue::Function { meta, .. } => meta.clone(),
267            KclValue::Module { meta, .. } => meta.clone(),
268            KclValue::KclNone { meta, .. } => meta.clone(),
269            KclValue::Type { meta, .. } => meta.clone(),
270        }
271    }
272
273    #[allow(unused)]
274    pub(crate) fn none() -> Self {
275        Self::KclNone {
276            value: Default::default(),
277            meta: Default::default(),
278        }
279    }
280
281    /// Human readable type name used in error messages.  Should not be relied
282    /// on for program logic.
283    pub(crate) fn human_friendly_type(&self) -> String {
284        if let Some(t) = self.principal_type() {
285            return t.to_string();
286        }
287        match self {
288            KclValue::Uuid { .. } => "Unique ID (uuid)",
289            KclValue::TagDeclarator(_) => "TagDeclarator",
290            KclValue::TagIdentifier(_) => "TagIdentifier",
291            KclValue::Solid { .. } => "Solid",
292            KclValue::Sketch { .. } => "Sketch",
293            KclValue::Helix { .. } => "Helix",
294            KclValue::ImportedGeometry(_) => "ImportedGeometry",
295            KclValue::Function { .. } => "Function",
296            KclValue::Plane { .. } => "Plane",
297            KclValue::Face { .. } => "Face",
298            KclValue::Bool { .. } => "boolean (true/false value)",
299            KclValue::Number {
300                ty: NumericType::Unknown,
301                ..
302            } => "number(unknown units)",
303            KclValue::Number {
304                ty: NumericType::Known(UnitType::Length(_)),
305                ..
306            } => "number(Length)",
307            KclValue::Number {
308                ty: NumericType::Known(UnitType::Angle(_)),
309                ..
310            } => "number(Angle)",
311            KclValue::Number { .. } => "number",
312            KclValue::String { .. } => "string (text)",
313            KclValue::Tuple { .. } => "tuple (list)",
314            KclValue::HomArray { .. } => "array (list)",
315            KclValue::Object { .. } => "object",
316            KclValue::Module { .. } => "module",
317            KclValue::Type { .. } => "type",
318            KclValue::KclNone { .. } => "None",
319        }
320        .to_owned()
321    }
322
323    pub(crate) fn from_literal(literal: Node<Literal>, exec_state: &mut ExecState) -> Self {
324        let meta = vec![literal.metadata()];
325        match literal.inner.value {
326            LiteralValue::Number { value, suffix } => {
327                let ty = NumericType::from_parsed(suffix, &exec_state.mod_local.settings);
328                if let NumericType::Default { len, .. } = &ty {
329                    if !exec_state.mod_local.explicit_length_units && *len != UnitLen::Mm {
330                        exec_state.warn(
331                            CompilationError::err(
332                                literal.as_source_range(),
333                                "Project-wide units are deprecated. Prefer to use per-file default units.",
334                            )
335                            .with_suggestion(
336                                "Fix by adding per-file settings",
337                                format!("@{SETTINGS}({SETTINGS_UNIT_LENGTH} = {len})\n"),
338                                // Insert at the start of the file.
339                                Some(SourceRange::new(0, 0, literal.module_id)),
340                                crate::errors::Tag::Deprecated,
341                            ),
342                        );
343                    }
344                }
345                KclValue::Number { value, meta, ty }
346            }
347            LiteralValue::String(value) => KclValue::String { value, meta },
348            LiteralValue::Bool(value) => KclValue::Bool { value, meta },
349        }
350    }
351
352    pub(crate) fn from_default_param(param: DefaultParamVal, exec_state: &mut ExecState) -> Self {
353        match param {
354            DefaultParamVal::Literal(lit) => Self::from_literal(lit, exec_state),
355            DefaultParamVal::KclNone(none) => KclValue::KclNone {
356                value: none,
357                meta: Default::default(),
358            },
359        }
360    }
361
362    pub(crate) fn map_env_ref(&self, old_env: usize, new_env: usize) -> Self {
363        let mut result = self.clone();
364        if let KclValue::Function {
365            value: FunctionSource::User { ref mut memory, .. },
366            ..
367        } = result
368        {
369            memory.replace_env(old_env, new_env);
370        }
371        result
372    }
373
374    pub const fn from_number_with_type(f: f64, ty: NumericType, meta: Vec<Metadata>) -> Self {
375        Self::Number { value: f, meta, ty }
376    }
377
378    /// Put the point into a KCL value.
379    pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
380        Self::Tuple {
381            value: vec![
382                Self::Number {
383                    value: p[0],
384                    meta: meta.clone(),
385                    ty: ty.clone(),
386                },
387                Self::Number {
388                    value: p[1],
389                    meta: meta.clone(),
390                    ty,
391                },
392            ],
393            meta,
394        }
395    }
396
397    pub(crate) fn as_usize(&self) -> Option<usize> {
398        match self {
399            KclValue::Number { value, .. } => crate::try_f64_to_usize(*value),
400            _ => None,
401        }
402    }
403
404    pub fn as_int(&self) -> Option<i64> {
405        match self {
406            KclValue::Number { value, .. } => crate::try_f64_to_i64(*value),
407            _ => None,
408        }
409    }
410
411    pub fn as_int_with_ty(&self) -> Option<(i64, NumericType)> {
412        match self {
413            KclValue::Number { value, ty, .. } => crate::try_f64_to_i64(*value).map(|i| (i, ty.clone())),
414            _ => None,
415        }
416    }
417
418    pub fn as_object(&self) -> Option<&KclObjectFields> {
419        if let KclValue::Object { value, meta: _ } = &self {
420            Some(value)
421        } else {
422            None
423        }
424    }
425
426    pub fn into_object(self) -> Option<KclObjectFields> {
427        if let KclValue::Object { value, meta: _ } = self {
428            Some(value)
429        } else {
430            None
431        }
432    }
433
434    pub fn as_str(&self) -> Option<&str> {
435        if let KclValue::String { value, meta: _ } = &self {
436            Some(value)
437        } else {
438            None
439        }
440    }
441
442    pub fn as_array(&self) -> Option<&[KclValue]> {
443        match self {
444            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => Some(value),
445            _ => None,
446        }
447    }
448
449    pub fn as_point2d(&self) -> Option<[TyF64; 2]> {
450        let arr = self.as_array()?;
451        if arr.len() != 2 {
452            return None;
453        }
454        let x = arr[0].as_ty_f64()?;
455        let y = arr[1].as_ty_f64()?;
456        Some([x, y])
457    }
458
459    pub fn as_point3d(&self) -> Option<[TyF64; 3]> {
460        let arr = self.as_array()?;
461        if arr.len() != 3 {
462            return None;
463        }
464        let x = arr[0].as_ty_f64()?;
465        let y = arr[1].as_ty_f64()?;
466        let z = arr[2].as_ty_f64()?;
467        Some([x, y, z])
468    }
469
470    pub fn as_uuid(&self) -> Option<uuid::Uuid> {
471        if let KclValue::Uuid { value, meta: _ } = &self {
472            Some(*value)
473        } else {
474            None
475        }
476    }
477
478    pub fn as_plane(&self) -> Option<&Plane> {
479        if let KclValue::Plane { value } = &self {
480            Some(value)
481        } else {
482            None
483        }
484    }
485
486    pub fn as_solid(&self) -> Option<&Solid> {
487        if let KclValue::Solid { value } = &self {
488            Some(value)
489        } else {
490            None
491        }
492    }
493
494    pub fn as_sketch(&self) -> Option<&Sketch> {
495        if let KclValue::Sketch { value } = self {
496            Some(value)
497        } else {
498            None
499        }
500    }
501
502    pub fn as_mut_sketch(&mut self) -> Option<&mut Sketch> {
503        if let KclValue::Sketch { value } = self {
504            Some(value)
505        } else {
506            None
507        }
508    }
509
510    pub fn as_mut_tag(&mut self) -> Option<&mut TagIdentifier> {
511        if let KclValue::TagIdentifier(value) = self {
512            Some(value)
513        } else {
514            None
515        }
516    }
517
518    #[cfg(test)]
519    pub fn as_f64(&self) -> Option<f64> {
520        if let KclValue::Number { value, .. } = &self {
521            Some(*value)
522        } else {
523            None
524        }
525    }
526
527    pub fn as_ty_f64(&self) -> Option<TyF64> {
528        if let KclValue::Number { value, ty, .. } = &self {
529            Some(TyF64::new(*value, ty.clone()))
530        } else {
531            None
532        }
533    }
534
535    pub fn as_bool(&self) -> Option<bool> {
536        if let KclValue::Bool { value, meta: _ } = &self {
537            Some(*value)
538        } else {
539            None
540        }
541    }
542
543    /// If this value fits in a u32, return it.
544    pub fn get_u32(&self, source_ranges: Vec<SourceRange>) -> Result<u32, KclError> {
545        let u = self.as_int().and_then(|n| u64::try_from(n).ok()).ok_or_else(|| {
546            KclError::Semantic(KclErrorDetails {
547                message: "Expected an integer >= 0".to_owned(),
548                source_ranges: source_ranges.clone(),
549            })
550        })?;
551        u32::try_from(u).map_err(|_| {
552            KclError::Semantic(KclErrorDetails {
553                message: "Number was too big".to_owned(),
554                source_ranges,
555            })
556        })
557    }
558
559    /// If this value is of type function, return it.
560    pub fn get_function(&self) -> Option<&FunctionSource> {
561        match self {
562            KclValue::Function { value, .. } => Some(value),
563            _ => None,
564        }
565    }
566
567    /// Get a tag identifier from a memory item.
568    pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> {
569        match self {
570            KclValue::TagIdentifier(t) => Ok(*t.clone()),
571            _ => Err(KclError::Semantic(KclErrorDetails {
572                message: format!("Not a tag identifier: {:?}", self),
573                source_ranges: self.clone().into(),
574            })),
575        }
576    }
577
578    /// Get a tag declarator from a memory item.
579    pub fn get_tag_declarator(&self) -> Result<TagNode, KclError> {
580        match self {
581            KclValue::TagDeclarator(t) => Ok((**t).clone()),
582            _ => Err(KclError::Semantic(KclErrorDetails {
583                message: format!("Not a tag declarator: {:?}", self),
584                source_ranges: self.clone().into(),
585            })),
586        }
587    }
588
589    /// If this KCL value is a bool, retrieve it.
590    pub fn get_bool(&self) -> Result<bool, KclError> {
591        let Self::Bool { value: b, .. } = self else {
592            return Err(KclError::Type(KclErrorDetails {
593                source_ranges: self.into(),
594                message: format!("Expected bool, found {}", self.human_friendly_type()),
595            }));
596        };
597        Ok(*b)
598    }
599
600    pub fn as_fn(&self) -> Option<&FunctionSource> {
601        match self {
602            KclValue::Function { value, .. } => Some(value),
603            _ => None,
604        }
605    }
606
607    pub fn value_str(&self) -> Option<String> {
608        match self {
609            KclValue::Bool { value, .. } => Some(format!("{value}")),
610            KclValue::Number { value, .. } => Some(format!("{value}")),
611            KclValue::String { value, .. } => Some(format!("'{value}'")),
612            KclValue::Uuid { value, .. } => Some(format!("{value}")),
613            KclValue::TagDeclarator(tag) => Some(format!("${}", tag.name)),
614            KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)),
615            // TODO better Array and Object stringification
616            KclValue::Tuple { .. } => Some("[...]".to_owned()),
617            KclValue::HomArray { .. } => Some("[...]".to_owned()),
618            KclValue::Object { .. } => Some("{ ... }".to_owned()),
619            KclValue::Module { .. }
620            | KclValue::Solid { .. }
621            | KclValue::Sketch { .. }
622            | KclValue::Helix { .. }
623            | KclValue::ImportedGeometry(_)
624            | KclValue::Function { .. }
625            | KclValue::Plane { .. }
626            | KclValue::Face { .. }
627            | KclValue::KclNone { .. }
628            | KclValue::Type { .. } => None,
629        }
630    }
631}
632
633impl From<Geometry> for KclValue {
634    fn from(value: Geometry) -> Self {
635        match value {
636            Geometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
637            Geometry::Solid(x) => Self::Solid { value: Box::new(x) },
638        }
639    }
640}
641
642impl From<GeometryWithImportedGeometry> for KclValue {
643    fn from(value: GeometryWithImportedGeometry) -> Self {
644        match value {
645            GeometryWithImportedGeometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
646            GeometryWithImportedGeometry::Solid(x) => Self::Solid { value: Box::new(x) },
647            GeometryWithImportedGeometry::ImportedGeometry(x) => Self::ImportedGeometry(*x),
648        }
649    }
650}