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