kcl_lib/execution/
kcl_value.rs

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