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    MixedArray {
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::MixedArray { 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::MixedArray { 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::MixedArray { 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) -> &'static str {
284        match self {
285            KclValue::Uuid { .. } => "Unique ID (uuid)",
286            KclValue::TagDeclarator(_) => "TagDeclarator",
287            KclValue::TagIdentifier(_) => "TagIdentifier",
288            KclValue::Solid { .. } => "Solid",
289            KclValue::Sketch { .. } => "Sketch",
290            KclValue::Helix { .. } => "Helix",
291            KclValue::ImportedGeometry(_) => "ImportedGeometry",
292            KclValue::Function { .. } => "Function",
293            KclValue::Plane { .. } => "Plane",
294            KclValue::Face { .. } => "Face",
295            KclValue::Bool { .. } => "boolean (true/false value)",
296            KclValue::Number {
297                ty: NumericType::Unknown,
298                ..
299            } => "number(unknown units)",
300            KclValue::Number {
301                ty: NumericType::Known(UnitType::Length(_)),
302                ..
303            } => "number(Length)",
304            KclValue::Number {
305                ty: NumericType::Known(UnitType::Angle(_)),
306                ..
307            } => "number(Angle)",
308            KclValue::Number { .. } => "number",
309            KclValue::String { .. } => "string (text)",
310            KclValue::MixedArray { .. } => "array (list)",
311            KclValue::HomArray { .. } => "array (list)",
312            KclValue::Object { .. } => "object",
313            KclValue::Module { .. } => "module",
314            KclValue::Type { .. } => "type",
315            KclValue::KclNone { .. } => "None",
316        }
317    }
318
319    pub(crate) fn from_literal(literal: Node<Literal>, exec_state: &mut ExecState) -> Self {
320        let meta = vec![literal.metadata()];
321        match literal.inner.value {
322            LiteralValue::Number { value, suffix } => {
323                let ty = NumericType::from_parsed(suffix, &exec_state.mod_local.settings);
324                if let NumericType::Default { len, .. } = &ty {
325                    if !exec_state.mod_local.explicit_length_units && *len != UnitLen::Mm {
326                        exec_state.warn(
327                            CompilationError::err(
328                                literal.as_source_range(),
329                                "Project-wide units are deprecated. Prefer to use per-file default units.",
330                            )
331                            .with_suggestion(
332                                "Fix by adding per-file settings",
333                                format!("@{SETTINGS}({SETTINGS_UNIT_LENGTH} = {len})\n"),
334                                // Insert at the start of the file.
335                                Some(SourceRange::new(0, 0, literal.module_id)),
336                                crate::errors::Tag::Deprecated,
337                            ),
338                        );
339                    }
340                }
341                KclValue::Number { value, meta, ty }
342            }
343            LiteralValue::String(value) => KclValue::String { value, meta },
344            LiteralValue::Bool(value) => KclValue::Bool { value, meta },
345        }
346    }
347
348    pub(crate) fn from_default_param(param: DefaultParamVal, exec_state: &mut ExecState) -> Self {
349        match param {
350            DefaultParamVal::Literal(lit) => Self::from_literal(lit, exec_state),
351            DefaultParamVal::KclNone(none) => KclValue::KclNone {
352                value: none,
353                meta: Default::default(),
354            },
355        }
356    }
357
358    pub(crate) fn map_env_ref(&self, old_env: usize, new_env: usize) -> Self {
359        let mut result = self.clone();
360        if let KclValue::Function {
361            value: FunctionSource::User { ref mut memory, .. },
362            ..
363        } = result
364        {
365            memory.replace_env(old_env, new_env);
366        }
367        result
368    }
369
370    pub const fn from_number_with_type(f: f64, ty: NumericType, meta: Vec<Metadata>) -> Self {
371        Self::Number { value: f, meta, ty }
372    }
373
374    /// Put the point into a KCL value.
375    pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
376        Self::MixedArray {
377            value: vec![
378                Self::Number {
379                    value: p[0],
380                    meta: meta.clone(),
381                    ty: ty.clone(),
382                },
383                Self::Number {
384                    value: p[1],
385                    meta: meta.clone(),
386                    ty,
387                },
388            ],
389            meta,
390        }
391    }
392
393    pub(crate) fn as_usize(&self) -> Option<usize> {
394        match self {
395            KclValue::Number { value, .. } => crate::try_f64_to_usize(*value),
396            _ => None,
397        }
398    }
399
400    pub fn as_int(&self) -> Option<i64> {
401        match self {
402            KclValue::Number { value, .. } => crate::try_f64_to_i64(*value),
403            _ => None,
404        }
405    }
406
407    pub fn as_object(&self) -> Option<&KclObjectFields> {
408        if let KclValue::Object { value, meta: _ } = &self {
409            Some(value)
410        } else {
411            None
412        }
413    }
414
415    pub fn into_object(self) -> Option<KclObjectFields> {
416        if let KclValue::Object { value, meta: _ } = self {
417            Some(value)
418        } else {
419            None
420        }
421    }
422
423    pub fn as_str(&self) -> Option<&str> {
424        if let KclValue::String { value, meta: _ } = &self {
425            Some(value)
426        } else {
427            None
428        }
429    }
430
431    pub fn as_array(&self) -> Option<&[KclValue]> {
432        match self {
433            KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => Some(value),
434            _ => None,
435        }
436    }
437
438    pub fn as_point2d(&self) -> Option<[TyF64; 2]> {
439        let arr = self.as_array()?;
440        if arr.len() != 2 {
441            return None;
442        }
443        let x = arr[0].as_ty_f64()?;
444        let y = arr[1].as_ty_f64()?;
445        Some([x, y])
446    }
447
448    pub fn as_point3d(&self) -> Option<[TyF64; 3]> {
449        let arr = self.as_array()?;
450        if arr.len() != 3 {
451            return None;
452        }
453        let x = arr[0].as_ty_f64()?;
454        let y = arr[1].as_ty_f64()?;
455        let z = arr[2].as_ty_f64()?;
456        Some([x, y, z])
457    }
458
459    pub fn as_uuid(&self) -> Option<uuid::Uuid> {
460        if let KclValue::Uuid { value, meta: _ } = &self {
461            Some(*value)
462        } else {
463            None
464        }
465    }
466
467    pub fn as_plane(&self) -> Option<&Plane> {
468        if let KclValue::Plane { value } = &self {
469            Some(value)
470        } else {
471            None
472        }
473    }
474
475    pub fn as_solid(&self) -> Option<&Solid> {
476        if let KclValue::Solid { value } = &self {
477            Some(value)
478        } else {
479            None
480        }
481    }
482
483    pub fn as_sketch(&self) -> Option<&Sketch> {
484        if let KclValue::Sketch { value } = self {
485            Some(value)
486        } else {
487            None
488        }
489    }
490
491    pub fn as_mut_sketch(&mut self) -> Option<&mut Sketch> {
492        if let KclValue::Sketch { value } = self {
493            Some(value)
494        } else {
495            None
496        }
497    }
498
499    pub fn as_mut_tag(&mut self) -> Option<&mut TagIdentifier> {
500        if let KclValue::TagIdentifier(value) = self {
501            Some(value)
502        } else {
503            None
504        }
505    }
506
507    #[cfg(test)]
508    pub fn as_f64(&self) -> Option<f64> {
509        if let KclValue::Number { value, .. } = &self {
510            Some(*value)
511        } else {
512            None
513        }
514    }
515
516    pub fn as_ty_f64(&self) -> Option<TyF64> {
517        if let KclValue::Number { value, ty, .. } = &self {
518            Some(TyF64::new(*value, ty.clone()))
519        } else {
520            None
521        }
522    }
523
524    pub fn as_bool(&self) -> Option<bool> {
525        if let KclValue::Bool { value, meta: _ } = &self {
526            Some(*value)
527        } else {
528            None
529        }
530    }
531
532    /// If this value fits in a u32, return it.
533    pub fn get_u32(&self, source_ranges: Vec<SourceRange>) -> Result<u32, KclError> {
534        let u = self.as_int().and_then(|n| u64::try_from(n).ok()).ok_or_else(|| {
535            KclError::Semantic(KclErrorDetails {
536                message: "Expected an integer >= 0".to_owned(),
537                source_ranges: source_ranges.clone(),
538            })
539        })?;
540        u32::try_from(u).map_err(|_| {
541            KclError::Semantic(KclErrorDetails {
542                message: "Number was too big".to_owned(),
543                source_ranges,
544            })
545        })
546    }
547
548    /// If this value is of type function, return it.
549    pub fn get_function(&self) -> Option<&FunctionSource> {
550        match self {
551            KclValue::Function { value, .. } => Some(value),
552            _ => None,
553        }
554    }
555
556    /// Get a tag identifier from a memory item.
557    pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> {
558        match self {
559            KclValue::TagIdentifier(t) => Ok(*t.clone()),
560            _ => Err(KclError::Semantic(KclErrorDetails {
561                message: format!("Not a tag identifier: {:?}", self),
562                source_ranges: self.clone().into(),
563            })),
564        }
565    }
566
567    /// Get a tag declarator from a memory item.
568    pub fn get_tag_declarator(&self) -> Result<TagNode, KclError> {
569        match self {
570            KclValue::TagDeclarator(t) => Ok((**t).clone()),
571            _ => Err(KclError::Semantic(KclErrorDetails {
572                message: format!("Not a tag declarator: {:?}", self),
573                source_ranges: self.clone().into(),
574            })),
575        }
576    }
577
578    /// If this KCL value is a bool, retrieve it.
579    pub fn get_bool(&self) -> Result<bool, KclError> {
580        let Self::Bool { value: b, .. } = self else {
581            return Err(KclError::Type(KclErrorDetails {
582                source_ranges: self.into(),
583                message: format!("Expected bool, found {}", self.human_friendly_type()),
584            }));
585        };
586        Ok(*b)
587    }
588
589    pub fn as_fn(&self) -> Option<&FunctionSource> {
590        match self {
591            KclValue::Function { value, .. } => Some(value),
592            _ => None,
593        }
594    }
595
596    pub fn value_str(&self) -> Option<String> {
597        match self {
598            KclValue::Bool { value, .. } => Some(format!("{value}")),
599            KclValue::Number { value, .. } => Some(format!("{value}")),
600            KclValue::String { value, .. } => Some(format!("'{value}'")),
601            KclValue::Uuid { value, .. } => Some(format!("{value}")),
602            KclValue::TagDeclarator(tag) => Some(format!("${}", tag.name)),
603            KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)),
604            // TODO better Array and Object stringification
605            KclValue::MixedArray { .. } => Some("[...]".to_owned()),
606            KclValue::HomArray { .. } => Some("[...]".to_owned()),
607            KclValue::Object { .. } => Some("{ ... }".to_owned()),
608            KclValue::Module { .. }
609            | KclValue::Solid { .. }
610            | KclValue::Sketch { .. }
611            | KclValue::Helix { .. }
612            | KclValue::ImportedGeometry(_)
613            | KclValue::Function { .. }
614            | KclValue::Plane { .. }
615            | KclValue::Face { .. }
616            | KclValue::KclNone { .. }
617            | KclValue::Type { .. } => None,
618        }
619    }
620}
621
622impl From<Geometry> for KclValue {
623    fn from(value: Geometry) -> Self {
624        match value {
625            Geometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
626            Geometry::Solid(x) => Self::Solid { value: Box::new(x) },
627        }
628    }
629}
630
631impl From<GeometryWithImportedGeometry> for KclValue {
632    fn from(value: GeometryWithImportedGeometry) -> Self {
633        match value {
634            GeometryWithImportedGeometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
635            GeometryWithImportedGeometry::Solid(x) => Self::Solid { value: Box::new(x) },
636            GeometryWithImportedGeometry::ImportedGeometry(x) => Self::ImportedGeometry(*x),
637        }
638    }
639}