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#[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 HomArray {
58 value: Vec<KclValue>,
59 #[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 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 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 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 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 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 pub fn get_function(&self) -> Option<&FunctionSource> {
550 match self {
551 KclValue::Function { value, .. } => Some(value),
552 _ => None,
553 }
554 }
555
556 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 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 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 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}