1use std::collections::HashMap;
2
3use async_recursion::async_recursion;
4use indexmap::IndexMap;
5use kcl_ezpz::Constraint;
6use kittycad_modeling_cmds::units::UnitLength;
7
8use crate::{
9 CompilationError, NodePath, SourceRange,
10 errors::{KclError, KclErrorDetails},
11 exec::UnitType,
12 execution::{
13 BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, ModelingCmdMeta, ModuleArtifactState,
14 Operation, PlaneType, StatementKind, TagIdentifier, annotations,
15 cad_op::OpKclValue,
16 fn_call::Args,
17 kcl_value::{FunctionSource, TypeDef},
18 memory,
19 state::{ModuleState, SketchBlockState},
20 types::{NumericType, PrimitiveType, RuntimeType},
21 },
22 fmt,
23 modules::{ModuleId, ModulePath, ModuleRepr},
24 parsing::ast::types::{
25 Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
26 BinaryPart, BodyItem, CodeBlock, Expr, IfExpression, ImportPath, ImportSelector, ItemVisibility,
27 MemberExpression, Name, Node, ObjectExpression, PipeExpression, Program, SketchBlock, SketchVar, TagDeclarator,
28 Type, UnaryExpression, UnaryOperator,
29 },
30 std::args::TyF64,
31};
32
33impl<'a> StatementKind<'a> {
34 fn expect_name(&self) -> &'a str {
35 match self {
36 StatementKind::Declaration { name } => name,
37 StatementKind::Expression => unreachable!(),
38 }
39 }
40}
41
42impl ExecutorContext {
43 async fn handle_annotations(
45 &self,
46 annotations: impl Iterator<Item = &Node<Annotation>>,
47 body_type: BodyType,
48 exec_state: &mut ExecState,
49 ) -> Result<bool, KclError> {
50 let mut no_prelude = false;
51 for annotation in annotations {
52 if annotation.name() == Some(annotations::SETTINGS) {
53 if matches!(body_type, BodyType::Root) {
54 let (updated_len, updated_angle) =
55 exec_state.mod_local.settings.update_from_annotation(annotation)?;
56 if updated_len {
57 exec_state.mod_local.explicit_length_units = true;
58 }
59 if updated_angle {
60 exec_state.warn(
61 CompilationError::err(
62 annotation.as_source_range(),
63 "Prefer to use explicit units for angles",
64 ),
65 annotations::WARN_ANGLE_UNITS,
66 );
67 }
68 } else {
69 exec_state.err(CompilationError::err(
70 annotation.as_source_range(),
71 "Settings can only be modified at the top level scope of a file",
72 ));
73 }
74 } else if annotation.name() == Some(annotations::NO_PRELUDE) {
75 if matches!(body_type, BodyType::Root) {
76 no_prelude = true;
77 } else {
78 exec_state.err(CompilationError::err(
79 annotation.as_source_range(),
80 "The standard library can only be skipped at the top level scope of a file",
81 ));
82 }
83 } else if annotation.name() == Some(annotations::WARNINGS) {
84 if matches!(body_type, BodyType::Root) {
86 let props = annotations::expect_properties(annotations::WARNINGS, annotation)?;
87 for p in props {
88 match &*p.inner.key.name {
89 annotations::WARN_ALLOW => {
90 let allowed = annotations::many_of(
91 &p.inner.value,
92 &annotations::WARN_VALUES,
93 annotation.as_source_range(),
94 )?;
95 exec_state.mod_local.allowed_warnings = allowed;
96 }
97 annotations::WARN_DENY => {
98 let denied = annotations::many_of(
99 &p.inner.value,
100 &annotations::WARN_VALUES,
101 annotation.as_source_range(),
102 )?;
103 exec_state.mod_local.denied_warnings = denied;
104 }
105 name => {
106 return Err(KclError::new_semantic(KclErrorDetails::new(
107 format!(
108 "Unexpected warnings key: `{name}`; expected one of `{}`, `{}`",
109 annotations::WARN_ALLOW,
110 annotations::WARN_DENY,
111 ),
112 vec![annotation.as_source_range()],
113 )));
114 }
115 }
116 }
117 } else {
118 exec_state.err(CompilationError::err(
119 annotation.as_source_range(),
120 "Warnings can only be customized at the top level scope of a file",
121 ));
122 }
123 } else {
124 exec_state.warn(
125 CompilationError::err(annotation.as_source_range(), "Unknown annotation"),
126 annotations::WARN_UNKNOWN_ATTR,
127 );
128 }
129 }
130 Ok(no_prelude)
131 }
132
133 pub(super) async fn exec_module_body(
134 &self,
135 program: &Node<Program>,
136 exec_state: &mut ExecState,
137 preserve_mem: bool,
138 module_id: ModuleId,
139 path: &ModulePath,
140 ) -> Result<
141 (Option<KclValue>, EnvironmentRef, Vec<String>, ModuleArtifactState),
142 (KclError, Option<EnvironmentRef>, Option<ModuleArtifactState>),
143 > {
144 crate::log::log(format!("enter module {path} {}", exec_state.stack()));
145
146 let mut local_state = ModuleState::new(path.clone(), exec_state.stack().memory.clone(), Some(module_id));
147 if !preserve_mem {
148 std::mem::swap(&mut exec_state.mod_local, &mut local_state);
149 }
150
151 let no_prelude = self
152 .handle_annotations(program.inner_attrs.iter(), crate::execution::BodyType::Root, exec_state)
153 .await
154 .map_err(|err| (err, None, None))?;
155
156 if !preserve_mem {
157 exec_state.mut_stack().push_new_root_env(!no_prelude);
158 }
159
160 let result = self
161 .exec_block(program, exec_state, crate::execution::BodyType::Root)
162 .await;
163
164 let env_ref = if preserve_mem {
165 exec_state.mut_stack().pop_and_preserve_env()
166 } else {
167 exec_state.mut_stack().pop_env()
168 };
169 let module_artifacts = if !preserve_mem {
170 std::mem::swap(&mut exec_state.mod_local, &mut local_state);
171 local_state.artifacts
172 } else {
173 std::mem::take(&mut exec_state.mod_local.artifacts)
174 };
175
176 crate::log::log(format!("leave {path}"));
177
178 result
179 .map_err(|err| (err, Some(env_ref), Some(module_artifacts.clone())))
180 .map(|result| (result, env_ref, local_state.module_exports, module_artifacts))
181 }
182
183 #[async_recursion]
185 pub(super) async fn exec_block<'a, B>(
186 &'a self,
187 block: &'a B,
188 exec_state: &mut ExecState,
189 body_type: BodyType,
190 ) -> Result<Option<KclValue>, KclError>
191 where
192 B: CodeBlock + Sync,
193 {
194 let mut last_expr = None;
195 for statement in block.body() {
197 match statement {
198 BodyItem::ImportStatement(import_stmt) => {
199 if !matches!(body_type, BodyType::Root) {
200 return Err(KclError::new_semantic(KclErrorDetails::new(
201 "Imports are only supported at the top-level of a file.".to_owned(),
202 vec![import_stmt.into()],
203 )));
204 }
205
206 let source_range = SourceRange::from(import_stmt);
207 let attrs = &import_stmt.outer_attrs;
208 let module_path = ModulePath::from_import_path(
209 &import_stmt.path,
210 &self.settings.project_directory,
211 &exec_state.mod_local.path,
212 )?;
213 let module_id = self
214 .open_module(&import_stmt.path, attrs, &module_path, exec_state, source_range)
215 .await?;
216
217 match &import_stmt.selector {
218 ImportSelector::List { items } => {
219 let (env_ref, module_exports) =
220 self.exec_module_for_items(module_id, exec_state, source_range).await?;
221 for import_item in items {
222 let mem = &exec_state.stack().memory;
224 let mut value = mem
225 .get_from(&import_item.name.name, env_ref, import_item.into(), 0)
226 .cloned();
227 let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.name.name);
228 let mut ty = mem.get_from(&ty_name, env_ref, import_item.into(), 0).cloned();
229 let mod_name = format!("{}{}", memory::MODULE_PREFIX, import_item.name.name);
230 let mut mod_value = mem.get_from(&mod_name, env_ref, import_item.into(), 0).cloned();
231
232 if value.is_err() && ty.is_err() && mod_value.is_err() {
233 return Err(KclError::new_undefined_value(
234 KclErrorDetails::new(
235 format!("{} is not defined in module", import_item.name.name),
236 vec![SourceRange::from(&import_item.name)],
237 ),
238 None,
239 ));
240 }
241
242 if value.is_ok() && !module_exports.contains(&import_item.name.name) {
244 value = Err(KclError::new_semantic(KclErrorDetails::new(
245 format!(
246 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
247 import_item.name.name
248 ),
249 vec![SourceRange::from(&import_item.name)],
250 )));
251 }
252
253 if ty.is_ok() && !module_exports.contains(&ty_name) {
254 ty = Err(KclError::new_semantic(KclErrorDetails::new(
255 format!(
256 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
257 import_item.name.name
258 ),
259 vec![SourceRange::from(&import_item.name)],
260 )));
261 }
262
263 if mod_value.is_ok() && !module_exports.contains(&mod_name) {
264 mod_value = Err(KclError::new_semantic(KclErrorDetails::new(
265 format!(
266 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
267 import_item.name.name
268 ),
269 vec![SourceRange::from(&import_item.name)],
270 )));
271 }
272
273 if value.is_err() && ty.is_err() && mod_value.is_err() {
274 return value.map(Option::Some);
275 }
276
277 if let Ok(value) = value {
279 exec_state.mut_stack().add(
280 import_item.identifier().to_owned(),
281 value,
282 SourceRange::from(&import_item.name),
283 )?;
284
285 if let ItemVisibility::Export = import_stmt.visibility {
286 exec_state
287 .mod_local
288 .module_exports
289 .push(import_item.identifier().to_owned());
290 }
291 }
292
293 if let Ok(ty) = ty {
294 let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.identifier());
295 exec_state.mut_stack().add(
296 ty_name.clone(),
297 ty,
298 SourceRange::from(&import_item.name),
299 )?;
300
301 if let ItemVisibility::Export = import_stmt.visibility {
302 exec_state.mod_local.module_exports.push(ty_name);
303 }
304 }
305
306 if let Ok(mod_value) = mod_value {
307 let mod_name = format!("{}{}", memory::MODULE_PREFIX, import_item.identifier());
308 exec_state.mut_stack().add(
309 mod_name.clone(),
310 mod_value,
311 SourceRange::from(&import_item.name),
312 )?;
313
314 if let ItemVisibility::Export = import_stmt.visibility {
315 exec_state.mod_local.module_exports.push(mod_name);
316 }
317 }
318 }
319 }
320 ImportSelector::Glob(_) => {
321 let (env_ref, module_exports) =
322 self.exec_module_for_items(module_id, exec_state, source_range).await?;
323 for name in module_exports.iter() {
324 let item = exec_state
325 .stack()
326 .memory
327 .get_from(name, env_ref, source_range, 0)
328 .map_err(|_err| {
329 KclError::new_internal(KclErrorDetails::new(
330 format!("{name} is not defined in module (but was exported?)"),
331 vec![source_range],
332 ))
333 })?
334 .clone();
335 exec_state.mut_stack().add(name.to_owned(), item, source_range)?;
336
337 if let ItemVisibility::Export = import_stmt.visibility {
338 exec_state.mod_local.module_exports.push(name.clone());
339 }
340 }
341 }
342 ImportSelector::None { .. } => {
343 let name = import_stmt.module_name().unwrap();
344 let item = KclValue::Module {
345 value: module_id,
346 meta: vec![source_range.into()],
347 };
348 exec_state.mut_stack().add(
349 format!("{}{}", memory::MODULE_PREFIX, name),
350 item,
351 source_range,
352 )?;
353 }
354 }
355 last_expr = None;
356 }
357 BodyItem::ExpressionStatement(expression_statement) => {
358 let metadata = Metadata::from(expression_statement);
359 last_expr = Some(
360 self.execute_expr(
361 &expression_statement.expression,
362 exec_state,
363 &metadata,
364 &[],
365 StatementKind::Expression,
366 )
367 .await?,
368 );
369 }
370 BodyItem::VariableDeclaration(variable_declaration) => {
371 let var_name = variable_declaration.declaration.id.name.to_string();
372 let source_range = SourceRange::from(&variable_declaration.declaration.init);
373 let metadata = Metadata { source_range };
374
375 let annotations = &variable_declaration.outer_attrs;
376
377 let lhs = variable_declaration.inner.name().to_owned();
380 let prev_being_declared = exec_state.mod_local.being_declared.take();
381 exec_state.mod_local.being_declared = Some(lhs);
382 let rhs_result = self
383 .execute_expr(
384 &variable_declaration.declaration.init,
385 exec_state,
386 &metadata,
387 annotations,
388 StatementKind::Declaration { name: &var_name },
389 )
390 .await;
391 exec_state.mod_local.being_declared = prev_being_declared;
393 let rhs = rhs_result?;
394
395 exec_state
396 .mut_stack()
397 .add(var_name.clone(), rhs.clone(), source_range)?;
398
399 if rhs.show_variable_in_feature_tree() {
400 exec_state.push_op(Operation::VariableDeclaration {
401 name: var_name.clone(),
402 value: OpKclValue::from(&rhs),
403 visibility: variable_declaration.visibility,
404 node_path: NodePath::placeholder(),
405 source_range,
406 });
407 }
408
409 if let ItemVisibility::Export = variable_declaration.visibility {
411 if matches!(body_type, BodyType::Root) {
412 exec_state.mod_local.module_exports.push(var_name);
413 } else {
414 exec_state.err(CompilationError::err(
415 variable_declaration.as_source_range(),
416 "Exports are only supported at the top-level of a file. Remove `export` or move it to the top-level.",
417 ));
418 }
419 }
420 last_expr = matches!(body_type, BodyType::Root).then_some(rhs);
422 }
423 BodyItem::TypeDeclaration(ty) => {
424 let metadata = Metadata::from(&**ty);
425 let attrs = annotations::get_fn_attrs(&ty.outer_attrs, metadata.source_range)?.unwrap_or_default();
426 match attrs.impl_ {
427 annotations::Impl::Rust | annotations::Impl::RustConstraint => {
428 let std_path = match &exec_state.mod_local.path {
429 ModulePath::Std { value } => value,
430 ModulePath::Local { .. } | ModulePath::Main => {
431 return Err(KclError::new_semantic(KclErrorDetails::new(
432 "User-defined types are not yet supported.".to_owned(),
433 vec![metadata.source_range],
434 )));
435 }
436 };
437 let (t, props) = crate::std::std_ty(std_path, &ty.name.name);
438 let value = KclValue::Type {
439 value: TypeDef::RustRepr(t, props),
440 meta: vec![metadata],
441 experimental: attrs.experimental,
442 };
443 let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
444 exec_state
445 .mut_stack()
446 .add(name_in_mem.clone(), value, metadata.source_range)
447 .map_err(|_| {
448 KclError::new_semantic(KclErrorDetails::new(
449 format!("Redefinition of type {}.", ty.name.name),
450 vec![metadata.source_range],
451 ))
452 })?;
453
454 if let ItemVisibility::Export = ty.visibility {
455 exec_state.mod_local.module_exports.push(name_in_mem);
456 }
457 }
458 annotations::Impl::Primitive => {}
460 annotations::Impl::Kcl | annotations::Impl::KclConstrainable => match &ty.alias {
461 Some(alias) => {
462 let value = KclValue::Type {
463 value: TypeDef::Alias(
464 RuntimeType::from_parsed(
465 alias.inner.clone(),
466 exec_state,
467 metadata.source_range,
468 attrs.impl_ == annotations::Impl::KclConstrainable,
469 )
470 .map_err(|e| KclError::new_semantic(e.into()))?,
471 ),
472 meta: vec![metadata],
473 experimental: attrs.experimental,
474 };
475 let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
476 exec_state
477 .mut_stack()
478 .add(name_in_mem.clone(), value, metadata.source_range)
479 .map_err(|_| {
480 KclError::new_semantic(KclErrorDetails::new(
481 format!("Redefinition of type {}.", ty.name.name),
482 vec![metadata.source_range],
483 ))
484 })?;
485
486 if let ItemVisibility::Export = ty.visibility {
487 exec_state.mod_local.module_exports.push(name_in_mem);
488 }
489 }
490 None => {
491 return Err(KclError::new_semantic(KclErrorDetails::new(
492 "User-defined types are not yet supported.".to_owned(),
493 vec![metadata.source_range],
494 )));
495 }
496 },
497 }
498
499 last_expr = None;
500 }
501 BodyItem::ReturnStatement(return_statement) => {
502 let metadata = Metadata::from(return_statement);
503
504 if matches!(body_type, BodyType::Root) {
505 return Err(KclError::new_semantic(KclErrorDetails::new(
506 "Cannot return from outside a function.".to_owned(),
507 vec![metadata.source_range],
508 )));
509 }
510
511 let value = self
512 .execute_expr(
513 &return_statement.argument,
514 exec_state,
515 &metadata,
516 &[],
517 StatementKind::Expression,
518 )
519 .await?;
520 exec_state
521 .mut_stack()
522 .add(memory::RETURN_NAME.to_owned(), value, metadata.source_range)
523 .map_err(|_| {
524 KclError::new_semantic(KclErrorDetails::new(
525 "Multiple returns from a single function.".to_owned(),
526 vec![metadata.source_range],
527 ))
528 })?;
529 last_expr = None;
530 }
531 }
532 }
533
534 if matches!(body_type, BodyType::Root) {
535 exec_state
537 .flush_batch(
538 ModelingCmdMeta::new(self, block.to_source_range()),
539 true,
542 )
543 .await?;
544 }
545
546 Ok(last_expr)
547 }
548
549 pub async fn open_module(
550 &self,
551 path: &ImportPath,
552 attrs: &[Node<Annotation>],
553 resolved_path: &ModulePath,
554 exec_state: &mut ExecState,
555 source_range: SourceRange,
556 ) -> Result<ModuleId, KclError> {
557 match path {
558 ImportPath::Kcl { .. } => {
559 exec_state.global.mod_loader.cycle_check(resolved_path, source_range)?;
560
561 if let Some(id) = exec_state.id_for_module(resolved_path) {
562 return Ok(id);
563 }
564
565 let id = exec_state.next_module_id();
566 exec_state.add_path_to_source_id(resolved_path.clone(), id);
568 let source = resolved_path.source(&self.fs, source_range).await?;
569 exec_state.add_id_to_source(id, source.clone());
570 let parsed = crate::parsing::parse_str(&source.source, id).parse_errs_as_err()?;
572 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Kcl(parsed, None));
573
574 Ok(id)
575 }
576 ImportPath::Foreign { .. } => {
577 if let Some(id) = exec_state.id_for_module(resolved_path) {
578 return Ok(id);
579 }
580
581 let id = exec_state.next_module_id();
582 let path = resolved_path.expect_path();
583 exec_state.add_path_to_source_id(resolved_path.clone(), id);
585 let format = super::import::format_from_annotations(attrs, path, source_range)?;
586 let geom = super::import::import_foreign(path, format, exec_state, self, source_range).await?;
587 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Foreign(geom, None));
588 Ok(id)
589 }
590 ImportPath::Std { .. } => {
591 if let Some(id) = exec_state.id_for_module(resolved_path) {
592 return Ok(id);
593 }
594
595 let id = exec_state.next_module_id();
596 exec_state.add_path_to_source_id(resolved_path.clone(), id);
598 let source = resolved_path.source(&self.fs, source_range).await?;
599 exec_state.add_id_to_source(id, source.clone());
600 let parsed = crate::parsing::parse_str(&source.source, id)
601 .parse_errs_as_err()
602 .unwrap();
603 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Kcl(parsed, None));
604 Ok(id)
605 }
606 }
607 }
608
609 pub(super) async fn exec_module_for_items(
610 &self,
611 module_id: ModuleId,
612 exec_state: &mut ExecState,
613 source_range: SourceRange,
614 ) -> Result<(EnvironmentRef, Vec<String>), KclError> {
615 let path = exec_state.global.module_infos[&module_id].path.clone();
616 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
617 let result = match &mut repr {
620 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
621 ModuleRepr::Kcl(_, Some((_, env_ref, items, _))) => Ok((*env_ref, items.clone())),
622 ModuleRepr::Kcl(program, cache) => self
623 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, false)
624 .await
625 .map(|(val, er, items, module_artifacts)| {
626 *cache = Some((val, er, items.clone(), module_artifacts.clone()));
627 (er, items)
628 }),
629 ModuleRepr::Foreign(geom, _) => Err(KclError::new_semantic(KclErrorDetails::new(
630 "Cannot import items from foreign modules".to_owned(),
631 vec![geom.source_range],
632 ))),
633 ModuleRepr::Dummy => unreachable!("Looking up {}, but it is still being interpreted", path),
634 };
635
636 exec_state.global.module_infos[&module_id].restore_repr(repr);
637 result
638 }
639
640 async fn exec_module_for_result(
641 &self,
642 module_id: ModuleId,
643 exec_state: &mut ExecState,
644 source_range: SourceRange,
645 ) -> Result<Option<KclValue>, KclError> {
646 let path = exec_state.global.module_infos[&module_id].path.clone();
647 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
648 let result = match &mut repr {
651 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
652 ModuleRepr::Kcl(_, Some((val, _, _, _))) => Ok(val.clone()),
653 ModuleRepr::Kcl(program, cached_items) => {
654 let result = self
655 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, false)
656 .await;
657 match result {
658 Ok((val, env, items, module_artifacts)) => {
659 *cached_items = Some((val.clone(), env, items, module_artifacts));
660 Ok(val)
661 }
662 Err(e) => Err(e),
663 }
664 }
665 ModuleRepr::Foreign(_, Some((imported, _))) => Ok(imported.clone()),
666 ModuleRepr::Foreign(geom, cached) => {
667 let result = super::import::send_to_engine(geom.clone(), exec_state, self)
668 .await
669 .map(|geom| Some(KclValue::ImportedGeometry(geom)));
670
671 match result {
672 Ok(val) => {
673 *cached = Some((val.clone(), exec_state.mod_local.artifacts.clone()));
674 Ok(val)
675 }
676 Err(e) => Err(e),
677 }
678 }
679 ModuleRepr::Dummy => unreachable!(),
680 };
681
682 exec_state.global.module_infos[&module_id].restore_repr(repr);
683
684 result
685 }
686
687 pub async fn exec_module_from_ast(
688 &self,
689 program: &Node<Program>,
690 module_id: ModuleId,
691 path: &ModulePath,
692 exec_state: &mut ExecState,
693 source_range: SourceRange,
694 preserve_mem: bool,
695 ) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>, ModuleArtifactState), KclError> {
696 exec_state.global.mod_loader.enter_module(path);
697 let result = self
698 .exec_module_body(program, exec_state, preserve_mem, module_id, path)
699 .await;
700 exec_state.global.mod_loader.leave_module(path);
701
702 result.map_err(|(err, _, _)| {
705 if let KclError::ImportCycle { .. } = err {
706 err.override_source_ranges(vec![source_range])
708 } else {
709 KclError::new_semantic(KclErrorDetails::new(
711 format!(
712 "Error loading imported file ({path}). Open it to view more details.\n {}",
713 err.message()
714 ),
715 vec![source_range],
716 ))
717 }
718 })
719 }
720
721 #[async_recursion]
722 pub(crate) async fn execute_expr<'a: 'async_recursion>(
723 &self,
724 init: &Expr,
725 exec_state: &mut ExecState,
726 metadata: &Metadata,
727 annotations: &[Node<Annotation>],
728 statement_kind: StatementKind<'a>,
729 ) -> Result<KclValue, KclError> {
730 let item = match init {
731 Expr::None(none) => KclValue::from(none),
732 Expr::Literal(literal) => KclValue::from_literal((**literal).clone(), exec_state),
733 Expr::TagDeclarator(tag) => tag.execute(exec_state).await?,
734 Expr::Name(name) => {
735 let being_declared = exec_state.mod_local.being_declared.clone();
736 let value = name
737 .get_result(exec_state, self)
738 .await
739 .map_err(|e| var_in_own_ref_err(e, &being_declared))?
740 .clone();
741 if let KclValue::Module { value: module_id, meta } = value {
742 self.exec_module_for_result(
743 module_id,
744 exec_state,
745 metadata.source_range
746 ).await?
747 .unwrap_or_else(|| {
748 exec_state.warn(CompilationError::err(
749 metadata.source_range,
750 "Imported module has no return value. The last statement of the module must be an expression, usually the Solid.",
751 ),
752 annotations::WARN_MOD_RETURN_VALUE);
753
754 let mut new_meta = vec![metadata.to_owned()];
755 new_meta.extend(meta);
756 KclValue::KclNone {
757 value: Default::default(),
758 meta: new_meta,
759 }
760 })
761 } else {
762 value
763 }
764 }
765 Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?,
766 Expr::FunctionExpression(function_expression) => {
767 let attrs = annotations::get_fn_attrs(annotations, metadata.source_range)?;
768 let experimental = attrs.map(|a| a.experimental).unwrap_or_default();
769 if let Some(attrs) = attrs
770 && (attrs.impl_ == annotations::Impl::Rust || attrs.impl_ == annotations::Impl::RustConstraint)
771 {
772 if let ModulePath::Std { value: std_path } = &exec_state.mod_local.path {
773 let (func, props) = crate::std::std_fn(std_path, statement_kind.expect_name());
774 KclValue::Function {
775 value: Box::new(FunctionSource::rust(func, function_expression.clone(), props, attrs)),
776 meta: vec![metadata.to_owned()],
777 }
778 } else {
779 return Err(KclError::new_semantic(KclErrorDetails::new(
780 "Rust implementation of functions is restricted to the standard library".to_owned(),
781 vec![metadata.source_range],
782 )));
783 }
784 } else {
785 KclValue::Function {
789 value: Box::new(FunctionSource::kcl(
790 function_expression.clone(),
791 exec_state.mut_stack().snapshot(),
792 matches!(&exec_state.mod_local.path, ModulePath::Std { .. }),
793 experimental,
794 )),
795 meta: vec![metadata.to_owned()],
796 }
797 }
798 }
799 Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?,
800 Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?,
801 Expr::PipeSubstitution(pipe_substitution) => match statement_kind {
802 StatementKind::Declaration { name } => {
803 let message = format!(
804 "you cannot declare variable {name} as %, because % can only be used in function calls"
805 );
806
807 return Err(KclError::new_semantic(KclErrorDetails::new(
808 message,
809 vec![pipe_substitution.into()],
810 )));
811 }
812 StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() {
813 Some(x) => x,
814 None => {
815 return Err(KclError::new_semantic(KclErrorDetails::new(
816 "cannot use % outside a pipe expression".to_owned(),
817 vec![pipe_substitution.into()],
818 )));
819 }
820 },
821 },
822 Expr::ArrayExpression(array_expression) => array_expression.execute(exec_state, self).await?,
823 Expr::ArrayRangeExpression(range_expression) => range_expression.execute(exec_state, self).await?,
824 Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?,
825 Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state, self).await?,
826 Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?,
827 Expr::IfExpression(expr) => expr.get_result(exec_state, self).await?,
828 Expr::LabelledExpression(expr) => {
829 let result = self
830 .execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind)
831 .await?;
832 exec_state
833 .mut_stack()
834 .add(expr.label.name.clone(), result.clone(), init.into())?;
835 result
837 }
838 Expr::AscribedExpression(expr) => expr.get_result(exec_state, self).await?,
839 Expr::SketchBlock(expr) => expr.get_result(exec_state, self).await?,
840 Expr::SketchVar(expr) => expr.get_result(exec_state, self).await?,
841 };
842 Ok(item)
843 }
844}
845
846fn var_in_own_ref_err(e: KclError, being_declared: &Option<String>) -> KclError {
849 let KclError::UndefinedValue { name, mut details } = e else {
850 return e;
851 };
852 if let (Some(name0), Some(name1)) = (&being_declared, &name)
856 && name0 == name1
857 {
858 details.message = format!(
859 "You can't use `{name0}` because you're currently trying to define it. Use a different variable here instead."
860 );
861 }
862 KclError::UndefinedValue { details, name }
863}
864
865impl Node<AscribedExpression> {
866 #[async_recursion]
867 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
868 let metadata = Metadata {
869 source_range: SourceRange::from(self),
870 };
871 let result = ctx
872 .execute_expr(&self.expr, exec_state, &metadata, &[], StatementKind::Expression)
873 .await?;
874 apply_ascription(&result, &self.ty, exec_state, self.into())
875 }
876}
877
878impl Node<SketchBlock> {
879 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
880 if exec_state.mod_local.sketch_block.is_some() {
881 return Err(KclError::new_semantic(KclErrorDetails::new(
883 "Cannot execute a sketch block from within another sketch block".to_owned(),
884 vec![SourceRange::from(self)],
885 )));
886 }
887
888 let (return_result, variables, sketch_block_state) = {
889 self.prep_mem(exec_state.mut_stack().snapshot(), exec_state);
891
892 let original_value = exec_state.mod_local.sketch_block.replace(SketchBlockState::default());
894
895 let result = ctx.exec_block(&self.body, exec_state, BodyType::Block).await;
896
897 let sketch_block_state = std::mem::replace(&mut exec_state.mod_local.sketch_block, original_value);
898
899 let block_variables = exec_state
900 .stack()
901 .find_all_in_current_env()
902 .map(|(name, value)| (name.clone(), value.clone()))
903 .collect::<IndexMap<_, _>>();
904
905 exec_state.mut_stack().pop_env();
906
907 (result, block_variables, sketch_block_state)
908 };
909
910 return_result?;
912 let Some(sketch_block_state) = sketch_block_state else {
913 debug_assert!(false, "Sketch block state should still be set to Some from just above");
914 return Err(KclError::new_internal(KclErrorDetails::new(
915 "Sketch block state should still be set to Some from just above".to_owned(),
916 vec![SourceRange::from(self)],
917 )));
918 };
919
920 let range = SourceRange::from(self);
922 let constraints = &sketch_block_state.constraints;
923 let initial_guesses = sketch_block_state
924 .sketch_vars
925 .iter()
926 .map(|v| {
927 let Some(sketch_var) = v.as_sketch_var() else {
928 return Err(KclError::new_internal(KclErrorDetails::new(
929 "Expected sketch variable".to_owned(),
930 vec![SourceRange::from(self)],
931 )));
932 };
933 let constraint_id = sketch_var.id.to_constraint_id(range)?;
934 let number_value = KclValue::Number {
936 value: sketch_var.initial_value,
937 ty: sketch_var.ty,
938 meta: sketch_var.meta.clone(),
939 };
940 let initial_guess_value =
941 normalize_to_solver_unit(&number_value, v.into(), exec_state, "sketch variable initial value")?;
942 let initial_guess = if let Some(n) = initial_guess_value.as_ty_f64() {
943 n.n
944 } else {
945 let message = format!(
946 "Expected number after coercion, but found {}",
947 initial_guess_value.human_friendly_type()
948 );
949 debug_assert!(false, "{}", &message);
950 return Err(KclError::new_internal(KclErrorDetails::new(
951 message,
952 vec![SourceRange::from(self)],
953 )));
954 };
955 Ok((constraint_id, initial_guess))
956 })
957 .collect::<Result<Vec<_>, KclError>>()?;
958 let config = kcl_ezpz::Config::default();
960 let solve_outcome = kcl_ezpz::solve(constraints, initial_guesses, config).map_err(|e| {
961 KclError::new_internal(KclErrorDetails::new(
962 format!("Error from constraint solver: {}", e.error),
963 vec![SourceRange::from(self)],
964 ))
965 })?;
966 for warning in &solve_outcome.warnings {
968 let message = if let Some(index) = warning.about_constraint.as_ref() {
969 format!("{}; constraint index {}", &warning.content, index)
970 } else {
971 format!("{}", &warning.content)
972 };
973 exec_state.warn(CompilationError::err(range, message), annotations::WARN_SOLVER);
974 }
975 let variables =
977 substitute_sketch_vars(variables, &solve_outcome.final_values, solver_numeric_type(exec_state))?;
978
979 let metadata = Metadata {
980 source_range: SourceRange::from(self),
981 };
982 Ok(KclValue::Object {
983 value: variables,
984 constrainable: Default::default(),
985 meta: vec![metadata],
986 })
987 }
988}
989
990fn solver_unit(exec_state: &ExecState) -> UnitLength {
991 exec_state.length_unit()
992}
993
994fn solver_numeric_type(exec_state: &ExecState) -> NumericType {
995 NumericType::Known(UnitType::Length(solver_unit(exec_state)))
996}
997
998fn normalize_to_solver_unit(
1001 value: &KclValue,
1002 source_range: SourceRange,
1003 exec_state: &mut ExecState,
1004 description: &str,
1005) -> Result<KclValue, KclError> {
1006 let length_ty = RuntimeType::Primitive(PrimitiveType::Number(solver_numeric_type(exec_state)));
1007 value.coerce(&length_ty, true, exec_state).map_err(|_| {
1008 KclError::new_semantic(KclErrorDetails::new(
1009 format!(
1010 "{} must be a length coercible to the module length unit {}, but found {}",
1011 description,
1012 length_ty.human_friendly_type(),
1013 value.human_friendly_type(),
1014 ),
1015 vec![source_range],
1016 ))
1017 })
1018}
1019
1020fn substitute_sketch_vars(
1021 variables: IndexMap<String, KclValue>,
1022 solutions: &[f64],
1023 solution_ty: NumericType,
1024) -> Result<HashMap<String, KclValue>, KclError> {
1025 let mut subbed = HashMap::with_capacity(variables.len());
1026 for (name, value) in variables {
1027 let subbed_value = substitute_sketch_var(value, solutions, solution_ty)?;
1028 subbed.insert(name, subbed_value);
1029 }
1030 Ok(subbed)
1031}
1032
1033fn substitute_sketch_var(value: KclValue, solutions: &[f64], solution_ty: NumericType) -> Result<KclValue, KclError> {
1034 match value {
1035 KclValue::Uuid { .. } => Ok(value),
1036 KclValue::Bool { .. } => Ok(value),
1037 KclValue::Number { .. } => Ok(value),
1038 KclValue::String { .. } => Ok(value),
1039 KclValue::SketchVar { value: var } => {
1040 let Some(solution) = solutions.get(var.id.0) else {
1041 let message = format!("No solution for sketch variable with id {}", var.id.0);
1042 debug_assert!(false, "{}", &message);
1043 return Err(KclError::new_internal(KclErrorDetails::new(
1044 message,
1045 var.meta.into_iter().map(|m| m.source_range).collect(),
1046 )));
1047 };
1048 Ok(KclValue::Number {
1049 value: *solution,
1050 ty: solution_ty,
1051 meta: var.meta.clone(),
1052 })
1053 }
1054 KclValue::Tuple { value, meta } => {
1055 let subbed = value
1056 .into_iter()
1057 .map(|v| substitute_sketch_var(v, solutions, solution_ty))
1058 .collect::<Result<Vec<_>, KclError>>()?;
1059 Ok(KclValue::Tuple { value: subbed, meta })
1060 }
1061 KclValue::HomArray { value, ty } => {
1062 let subbed = value
1063 .into_iter()
1064 .map(|v| substitute_sketch_var(v, solutions, solution_ty))
1065 .collect::<Result<Vec<_>, KclError>>()?;
1066 Ok(KclValue::HomArray { value: subbed, ty })
1067 }
1068 KclValue::Object {
1069 value,
1070 constrainable,
1071 meta,
1072 } => {
1073 let subbed = value
1074 .into_iter()
1075 .map(|(k, v)| substitute_sketch_var(v, solutions, solution_ty).map(|v| (k, v)))
1076 .collect::<Result<HashMap<_, _>, KclError>>()?;
1077 Ok(KclValue::Object {
1078 value: subbed,
1079 constrainable,
1080 meta,
1081 })
1082 }
1083 KclValue::TagIdentifier(_) => Ok(value),
1084 KclValue::TagDeclarator(_) => Ok(value),
1085 KclValue::GdtAnnotation { .. } => Ok(value),
1086 KclValue::Plane { .. } => Ok(value),
1087 KclValue::Face { .. } => Ok(value),
1088 KclValue::Sketch { .. } => Ok(value),
1089 KclValue::Solid { .. } => Ok(value),
1090 KclValue::Helix { .. } => Ok(value),
1091 KclValue::ImportedGeometry(_) => Ok(value),
1092 KclValue::Function { .. } => Ok(value),
1093 KclValue::Module { .. } => Ok(value),
1094 KclValue::Type { .. } => Ok(value),
1095 KclValue::KclNone { .. } => Ok(value),
1096 }
1097}
1098
1099impl SketchBlock {
1100 fn prep_mem(&self, parent: EnvironmentRef, exec_state: &mut ExecState) {
1101 exec_state.mut_stack().push_new_env_for_call(parent);
1102 }
1103}
1104
1105impl Node<SketchVar> {
1106 pub async fn get_result(&self, exec_state: &mut ExecState, _ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1107 let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
1108 return Err(KclError::new_semantic(KclErrorDetails::new(
1109 "Cannot use a sketch variable outside of a sketch block".to_owned(),
1110 vec![SourceRange::from(self)],
1111 )));
1112 };
1113 let id = sketch_block_state.next_sketch_var_id();
1114 let sketch_var = if let Some(initial) = &self.initial {
1115 KclValue::from_sketch_var_literal(initial, id, exec_state)
1116 } else {
1117 let metadata = Metadata {
1118 source_range: SourceRange::from(self),
1119 };
1120
1121 KclValue::SketchVar {
1122 value: Box::new(super::SketchVar {
1123 id,
1124 initial_value: 0.0,
1125 ty: NumericType::default(),
1126 meta: vec![metadata],
1127 }),
1128 }
1129 };
1130
1131 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
1132 return Err(KclError::new_semantic(KclErrorDetails::new(
1133 "Cannot use a sketch variable outside of a sketch block".to_owned(),
1134 vec![SourceRange::from(self)],
1135 )));
1136 };
1137 sketch_block_state.sketch_vars.push(sketch_var.clone());
1138
1139 Ok(sketch_var)
1140 }
1141}
1142
1143fn apply_ascription(
1144 value: &KclValue,
1145 ty: &Node<Type>,
1146 exec_state: &mut ExecState,
1147 source_range: SourceRange,
1148) -> Result<KclValue, KclError> {
1149 let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into(), false)
1150 .map_err(|e| KclError::new_semantic(e.into()))?;
1151
1152 if matches!(&ty, &RuntimeType::Primitive(PrimitiveType::Number(..))) {
1153 exec_state.clear_units_warnings(&source_range);
1154 }
1155
1156 value.coerce(&ty, false, exec_state).map_err(|_| {
1157 let suggestion = if ty == RuntimeType::length() {
1158 ", you might try coercing to a fully specified numeric type such as `mm`"
1159 } else if ty == RuntimeType::angle() {
1160 ", you might try coercing to a fully specified numeric type such as `deg`"
1161 } else {
1162 ""
1163 };
1164 let ty_str = if let Some(ty) = value.principal_type() {
1165 format!("(with type `{ty}`) ")
1166 } else {
1167 String::new()
1168 };
1169 KclError::new_semantic(KclErrorDetails::new(
1170 format!(
1171 "could not coerce {} {ty_str}to type `{ty}`{suggestion}",
1172 value.human_friendly_type()
1173 ),
1174 vec![source_range],
1175 ))
1176 })
1177}
1178
1179impl BinaryPart {
1180 #[async_recursion]
1181 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1182 match self {
1183 BinaryPart::Literal(literal) => Ok(KclValue::from_literal((**literal).clone(), exec_state)),
1184 BinaryPart::Name(name) => name.get_result(exec_state, ctx).await.cloned(),
1185 BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
1186 BinaryPart::CallExpressionKw(call_expression) => call_expression.execute(exec_state, ctx).await,
1187 BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
1188 BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state, ctx).await,
1189 BinaryPart::ArrayExpression(e) => e.execute(exec_state, ctx).await,
1190 BinaryPart::ArrayRangeExpression(e) => e.execute(exec_state, ctx).await,
1191 BinaryPart::ObjectExpression(e) => e.execute(exec_state, ctx).await,
1192 BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
1193 BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
1194 BinaryPart::SketchVar(e) => e.get_result(exec_state, ctx).await,
1195 }
1196 }
1197}
1198
1199impl Node<Name> {
1200 pub(super) async fn get_result<'a>(
1201 &self,
1202 exec_state: &'a mut ExecState,
1203 ctx: &ExecutorContext,
1204 ) -> Result<&'a KclValue, KclError> {
1205 let being_declared = exec_state.mod_local.being_declared.clone();
1206 self.get_result_inner(exec_state, ctx)
1207 .await
1208 .map_err(|e| var_in_own_ref_err(e, &being_declared))
1209 }
1210
1211 async fn get_result_inner<'a>(
1212 &self,
1213 exec_state: &'a mut ExecState,
1214 ctx: &ExecutorContext,
1215 ) -> Result<&'a KclValue, KclError> {
1216 if self.abs_path {
1217 return Err(KclError::new_semantic(KclErrorDetails::new(
1218 "Absolute paths (names beginning with `::` are not yet supported)".to_owned(),
1219 self.as_source_ranges(),
1220 )));
1221 }
1222
1223 let mod_name = format!("{}{}", memory::MODULE_PREFIX, self.name.name);
1224
1225 if self.path.is_empty() {
1226 let item_value = exec_state.stack().get(&self.name.name, self.into());
1227 if item_value.is_ok() {
1228 return item_value;
1229 }
1230 return exec_state.stack().get(&mod_name, self.into());
1231 }
1232
1233 let mut mem_spec: Option<(EnvironmentRef, Vec<String>)> = None;
1234 for p in &self.path {
1235 let value = match mem_spec {
1236 Some((env, exports)) => {
1237 if !exports.contains(&p.name) {
1238 return Err(KclError::new_semantic(KclErrorDetails::new(
1239 format!("Item {} not found in module's exported items", p.name),
1240 p.as_source_ranges(),
1241 )));
1242 }
1243
1244 exec_state
1245 .stack()
1246 .memory
1247 .get_from(&p.name, env, p.as_source_range(), 0)?
1248 }
1249 None => exec_state
1250 .stack()
1251 .get(&format!("{}{}", memory::MODULE_PREFIX, p.name), self.into())?,
1252 };
1253
1254 let KclValue::Module { value: module_id, .. } = value else {
1255 return Err(KclError::new_semantic(KclErrorDetails::new(
1256 format!(
1257 "Identifier in path must refer to a module, found {}",
1258 value.human_friendly_type()
1259 ),
1260 p.as_source_ranges(),
1261 )));
1262 };
1263
1264 mem_spec = Some(
1265 ctx.exec_module_for_items(*module_id, exec_state, p.as_source_range())
1266 .await?,
1267 );
1268 }
1269
1270 let (env, exports) = mem_spec.unwrap();
1271
1272 let item_exported = exports.contains(&self.name.name);
1273 let item_value = exec_state
1274 .stack()
1275 .memory
1276 .get_from(&self.name.name, env, self.name.as_source_range(), 0);
1277
1278 if item_exported && item_value.is_ok() {
1280 return item_value;
1281 }
1282
1283 let mod_exported = exports.contains(&mod_name);
1284 let mod_value = exec_state
1285 .stack()
1286 .memory
1287 .get_from(&mod_name, env, self.name.as_source_range(), 0);
1288
1289 if mod_exported && mod_value.is_ok() {
1291 return mod_value;
1292 }
1293
1294 if item_value.is_err() && mod_value.is_err() {
1296 return item_value;
1297 }
1298
1299 debug_assert!((item_value.is_ok() && !item_exported) || (mod_value.is_ok() && !mod_exported));
1301 Err(KclError::new_semantic(KclErrorDetails::new(
1302 format!("Item {} not found in module's exported items", self.name.name),
1303 self.name.as_source_ranges(),
1304 )))
1305 }
1306}
1307
1308impl Node<MemberExpression> {
1309 async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1310 let meta = Metadata {
1311 source_range: SourceRange::from(self),
1312 };
1313 let property = Property::try_from(
1314 self.computed,
1315 self.property.clone(),
1316 exec_state,
1317 self.into(),
1318 ctx,
1319 &meta,
1320 &[],
1321 StatementKind::Expression,
1322 )
1323 .await?;
1324 let object = ctx
1325 .execute_expr(&self.object, exec_state, &meta, &[], StatementKind::Expression)
1326 .await?;
1327
1328 match (object, property, self.computed) {
1330 (KclValue::Plane { value: plane }, Property::String(property), false) => match property.as_str() {
1331 "zAxis" => {
1332 let (p, u) = plane.info.z_axis.as_3_dims();
1333 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]))
1334 }
1335 "yAxis" => {
1336 let (p, u) = plane.info.y_axis.as_3_dims();
1337 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]))
1338 }
1339 "xAxis" => {
1340 let (p, u) = plane.info.x_axis.as_3_dims();
1341 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]))
1342 }
1343 "origin" => {
1344 let (p, u) = plane.info.origin.as_3_dims();
1345 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]))
1346 }
1347 other => Err(KclError::new_undefined_value(
1348 KclErrorDetails::new(
1349 format!("Property '{other}' not found in plane"),
1350 vec![self.clone().into()],
1351 ),
1352 None,
1353 )),
1354 },
1355 (KclValue::Object { value: map, .. }, Property::String(property), false) => {
1356 if let Some(value) = map.get(&property) {
1357 Ok(value.to_owned())
1358 } else {
1359 Err(KclError::new_undefined_value(
1360 KclErrorDetails::new(
1361 format!("Property '{property}' not found in object"),
1362 vec![self.clone().into()],
1363 ),
1364 None,
1365 ))
1366 }
1367 }
1368 (KclValue::Object { .. }, Property::String(property), true) => {
1369 Err(KclError::new_semantic(KclErrorDetails::new(
1370 format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
1371 vec![self.clone().into()],
1372 )))
1373 }
1374 (KclValue::Object { value: map, .. }, p @ Property::UInt(i), _) => {
1375 if i == 0
1376 && let Some(value) = map.get("x")
1377 {
1378 return Ok(value.to_owned());
1379 }
1380 if i == 1
1381 && let Some(value) = map.get("y")
1382 {
1383 return Ok(value.to_owned());
1384 }
1385 if i == 2
1386 && let Some(value) = map.get("z")
1387 {
1388 return Ok(value.to_owned());
1389 }
1390 let t = p.type_name();
1391 let article = article_for(t);
1392 Err(KclError::new_semantic(KclErrorDetails::new(
1393 format!("Only strings can be used as the property of an object, but you're using {article} {t}",),
1394 vec![self.clone().into()],
1395 )))
1396 }
1397 (KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => {
1398 let value_of_arr = arr.get(index);
1399 if let Some(value) = value_of_arr {
1400 Ok(value.to_owned())
1401 } else {
1402 Err(KclError::new_undefined_value(
1403 KclErrorDetails::new(
1404 format!("The array doesn't have any item at index {index}"),
1405 vec![self.clone().into()],
1406 ),
1407 None,
1408 ))
1409 }
1410 }
1411 (obj, Property::UInt(0), _) => Ok(obj),
1414 (KclValue::HomArray { .. }, p, _) => {
1415 let t = p.type_name();
1416 let article = article_for(t);
1417 Err(KclError::new_semantic(KclErrorDetails::new(
1418 format!("Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",),
1419 vec![self.clone().into()],
1420 )))
1421 }
1422 (KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => Ok(KclValue::Sketch {
1423 value: Box::new(value.sketch),
1424 }),
1425 (geometry @ KclValue::Solid { .. }, Property::String(prop), false) if prop == "tags" => {
1426 Err(KclError::new_semantic(KclErrorDetails::new(
1428 format!(
1429 "Property `{prop}` not found on {}. You can get a solid's tags through its sketch, as in, `exampleSolid.sketch.tags`.",
1430 geometry.human_friendly_type()
1431 ),
1432 vec![self.clone().into()],
1433 )))
1434 }
1435 (KclValue::Sketch { value: sk }, Property::String(prop), false) if prop == "tags" => Ok(KclValue::Object {
1436 meta: vec![Metadata {
1437 source_range: SourceRange::from(self.clone()),
1438 }],
1439 value: sk
1440 .tags
1441 .iter()
1442 .map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
1443 .collect(),
1444 constrainable: false,
1445 }),
1446 (geometry @ (KclValue::Sketch { .. } | KclValue::Solid { .. }), Property::String(property), false) => {
1447 Err(KclError::new_semantic(KclErrorDetails::new(
1448 format!("Property `{property}` not found on {}", geometry.human_friendly_type()),
1449 vec![self.clone().into()],
1450 )))
1451 }
1452 (being_indexed, _, _) => Err(KclError::new_semantic(KclErrorDetails::new(
1453 format!(
1454 "Only arrays can be indexed, but you're trying to index {}",
1455 being_indexed.human_friendly_type()
1456 ),
1457 vec![self.clone().into()],
1458 ))),
1459 }
1460 }
1461}
1462
1463impl Node<BinaryExpression> {
1464 #[async_recursion]
1465 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1466 let left_value = self.left.get_result(exec_state, ctx).await?;
1467 let right_value = self.right.get_result(exec_state, ctx).await?;
1468 let mut meta = left_value.metadata();
1469 meta.extend(right_value.metadata());
1470
1471 if self.operator == BinaryOperator::Add
1473 && let (KclValue::String { value: left, meta: _ }, KclValue::String { value: right, meta: _ }) =
1474 (&left_value, &right_value)
1475 {
1476 return Ok(KclValue::String {
1477 value: format!("{left}{right}"),
1478 meta,
1479 });
1480 }
1481
1482 if self.operator == BinaryOperator::Add || self.operator == BinaryOperator::Or {
1484 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
1485 let args = Args::new_no_args(self.into(), ctx.clone());
1486 let result = crate::std::csg::inner_union(
1487 vec![*left.clone(), *right.clone()],
1488 Default::default(),
1489 exec_state,
1490 args,
1491 )
1492 .await?;
1493 return Ok(result.into());
1494 }
1495 } else if self.operator == BinaryOperator::Sub {
1496 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
1498 let args = Args::new_no_args(self.into(), ctx.clone());
1499 let result = crate::std::csg::inner_subtract(
1500 vec![*left.clone()],
1501 vec![*right.clone()],
1502 Default::default(),
1503 exec_state,
1504 args,
1505 )
1506 .await?;
1507 return Ok(result.into());
1508 }
1509 } else if self.operator == BinaryOperator::And {
1510 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
1512 let args = Args::new_no_args(self.into(), ctx.clone());
1513 let result = crate::std::csg::inner_intersect(
1514 vec![*left.clone(), *right.clone()],
1515 Default::default(),
1516 exec_state,
1517 args,
1518 )
1519 .await?;
1520 return Ok(result.into());
1521 }
1522 }
1523
1524 if self.operator == BinaryOperator::Or || self.operator == BinaryOperator::And {
1526 let KclValue::Bool {
1527 value: left_value,
1528 meta: _,
1529 } = left_value
1530 else {
1531 return Err(KclError::new_semantic(KclErrorDetails::new(
1532 format!(
1533 "Cannot apply logical operator to non-boolean value: {}",
1534 left_value.human_friendly_type()
1535 ),
1536 vec![self.left.clone().into()],
1537 )));
1538 };
1539 let KclValue::Bool {
1540 value: right_value,
1541 meta: _,
1542 } = right_value
1543 else {
1544 return Err(KclError::new_semantic(KclErrorDetails::new(
1545 format!(
1546 "Cannot apply logical operator to non-boolean value: {}",
1547 right_value.human_friendly_type()
1548 ),
1549 vec![self.right.clone().into()],
1550 )));
1551 };
1552 let raw_value = match self.operator {
1553 BinaryOperator::Or => left_value || right_value,
1554 BinaryOperator::And => left_value && right_value,
1555 _ => unreachable!(),
1556 };
1557 return Ok(KclValue::Bool { value: raw_value, meta });
1558 }
1559
1560 if self.operator == BinaryOperator::Eq && exec_state.mod_local.sketch_block.is_some() {
1562 match (&left_value, &right_value) {
1563 (KclValue::SketchVar { value: left_value, .. }, KclValue::SketchVar { value: right_value, .. })
1565 if left_value.id == right_value.id =>
1566 {
1567 return Ok(KclValue::Bool { value: true, meta });
1568 }
1569 (KclValue::SketchVar { .. }, KclValue::SketchVar { .. }) => {
1571 return Err(KclError::new_semantic(KclErrorDetails::new(
1574 "TODO: Different sketch variables".to_owned(),
1575 vec![self.into()],
1576 )));
1577 }
1578 (KclValue::SketchVar { value: var, .. }, input_number @ KclValue::Number { .. })
1580 | (input_number @ KclValue::Number { .. }, KclValue::SketchVar { value: var, .. }) => {
1581 let number_value = normalize_to_solver_unit(
1582 input_number,
1583 input_number.into(),
1584 exec_state,
1585 "fixed constraint value",
1586 )?;
1587 let Some(n) = number_value.as_ty_f64() else {
1588 let message = format!(
1589 "Expected number after coercion, but found {}",
1590 number_value.human_friendly_type()
1591 );
1592 debug_assert!(false, "{}", &message);
1593 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![self.into()])));
1594 };
1595 let constraint = Constraint::Fixed(var.id.to_constraint_id(self.as_source_range())?, n.n);
1596 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
1597 let message = "Being inside a sketch block should have already been checked above".to_owned();
1598 debug_assert!(false, "{}", &message);
1599 return Err(KclError::new_internal(KclErrorDetails::new(
1600 message,
1601 vec![SourceRange::from(self)],
1602 )));
1603 };
1604 sketch_block_state.constraints.push(constraint);
1605 return Ok(KclValue::Bool { value: true, meta });
1606 }
1607 _ => {
1608 return Err(KclError::new_semantic(KclErrorDetails::new(
1609 format!(
1610 "Cannot create an equivalence constraint between values of these types: {} and {}",
1611 left_value.human_friendly_type(),
1612 right_value.human_friendly_type()
1613 ),
1614 vec![self.into()],
1615 )));
1616 }
1617 }
1618 }
1619
1620 let left = number_as_f64(&left_value, self.left.clone().into())?;
1621 let right = number_as_f64(&right_value, self.right.clone().into())?;
1622
1623 let value = match self.operator {
1624 BinaryOperator::Add => {
1625 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
1626 self.warn_on_unknown(&ty, "Adding", exec_state);
1627 KclValue::Number { value: l + r, meta, ty }
1628 }
1629 BinaryOperator::Sub => {
1630 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
1631 self.warn_on_unknown(&ty, "Subtracting", exec_state);
1632 KclValue::Number { value: l - r, meta, ty }
1633 }
1634 BinaryOperator::Mul => {
1635 let (l, r, ty) = NumericType::combine_mul(left, right);
1636 self.warn_on_unknown(&ty, "Multiplying", exec_state);
1637 KclValue::Number { value: l * r, meta, ty }
1638 }
1639 BinaryOperator::Div => {
1640 let (l, r, ty) = NumericType::combine_div(left, right);
1641 self.warn_on_unknown(&ty, "Dividing", exec_state);
1642 KclValue::Number { value: l / r, meta, ty }
1643 }
1644 BinaryOperator::Mod => {
1645 let (l, r, ty) = NumericType::combine_mod(left, right);
1646 self.warn_on_unknown(&ty, "Modulo of", exec_state);
1647 KclValue::Number { value: l % r, meta, ty }
1648 }
1649 BinaryOperator::Pow => KclValue::Number {
1650 value: left.n.powf(right.n),
1651 meta,
1652 ty: exec_state.current_default_units(),
1653 },
1654 BinaryOperator::Neq => {
1655 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
1656 self.warn_on_unknown(&ty, "Comparing", exec_state);
1657 KclValue::Bool { value: l != r, meta }
1658 }
1659 BinaryOperator::Gt => {
1660 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
1661 self.warn_on_unknown(&ty, "Comparing", exec_state);
1662 KclValue::Bool { value: l > r, meta }
1663 }
1664 BinaryOperator::Gte => {
1665 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
1666 self.warn_on_unknown(&ty, "Comparing", exec_state);
1667 KclValue::Bool { value: l >= r, meta }
1668 }
1669 BinaryOperator::Lt => {
1670 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
1671 self.warn_on_unknown(&ty, "Comparing", exec_state);
1672 KclValue::Bool { value: l < r, meta }
1673 }
1674 BinaryOperator::Lte => {
1675 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
1676 self.warn_on_unknown(&ty, "Comparing", exec_state);
1677 KclValue::Bool { value: l <= r, meta }
1678 }
1679 BinaryOperator::Eq => {
1680 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
1681 self.warn_on_unknown(&ty, "Comparing", exec_state);
1682 KclValue::Bool { value: l == r, meta }
1683 }
1684 BinaryOperator::And | BinaryOperator::Or => unreachable!(),
1685 };
1686
1687 Ok(value)
1688 }
1689
1690 fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
1691 if ty == &NumericType::Unknown {
1692 let sr = self.as_source_range();
1693 exec_state.clear_units_warnings(&sr);
1694 let mut err = CompilationError::err(
1695 sr,
1696 format!(
1697 "{verb} numbers which have unknown or incompatible units.\nYou can probably fix this error by specifying the units using type ascription, e.g., `len: number(mm)` or `(a * b): number(deg)`."
1698 ),
1699 );
1700 err.tag = crate::errors::Tag::UnknownNumericUnits;
1701 exec_state.warn(err, annotations::WARN_UNKNOWN_UNITS);
1702 }
1703 }
1704}
1705
1706impl Node<UnaryExpression> {
1707 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1708 if self.operator == UnaryOperator::Not {
1709 let value = self.argument.get_result(exec_state, ctx).await?;
1710 let KclValue::Bool {
1711 value: bool_value,
1712 meta: _,
1713 } = value
1714 else {
1715 return Err(KclError::new_semantic(KclErrorDetails::new(
1716 format!(
1717 "Cannot apply unary operator ! to non-boolean value: {}",
1718 value.human_friendly_type()
1719 ),
1720 vec![self.into()],
1721 )));
1722 };
1723 let meta = vec![Metadata {
1724 source_range: self.into(),
1725 }];
1726 let negated = KclValue::Bool {
1727 value: !bool_value,
1728 meta,
1729 };
1730
1731 return Ok(negated);
1732 }
1733
1734 let value = &self.argument.get_result(exec_state, ctx).await?;
1735 let err = || {
1736 KclError::new_semantic(KclErrorDetails::new(
1737 format!(
1738 "You can only negate numbers, planes, or lines, but this is a {}",
1739 value.human_friendly_type()
1740 ),
1741 vec![self.into()],
1742 ))
1743 };
1744 match value {
1745 KclValue::Number { value, ty, .. } => {
1746 let meta = vec![Metadata {
1747 source_range: self.into(),
1748 }];
1749 Ok(KclValue::Number {
1750 value: -value,
1751 meta,
1752 ty: *ty,
1753 })
1754 }
1755 KclValue::Plane { value } => {
1756 let mut plane = value.clone();
1757 if plane.info.x_axis.x != 0.0 {
1758 plane.info.x_axis.x *= -1.0;
1759 }
1760 if plane.info.x_axis.y != 0.0 {
1761 plane.info.x_axis.y *= -1.0;
1762 }
1763 if plane.info.x_axis.z != 0.0 {
1764 plane.info.x_axis.z *= -1.0;
1765 }
1766
1767 plane.value = PlaneType::Uninit;
1768 plane.id = exec_state.next_uuid();
1769 Ok(KclValue::Plane { value: plane })
1770 }
1771 KclValue::Object {
1772 value: values, meta, ..
1773 } => {
1774 let Some(direction) = values.get("direction") else {
1776 return Err(err());
1777 };
1778
1779 let direction = match direction {
1780 KclValue::Tuple { value: values, meta } => {
1781 let values = values
1782 .iter()
1783 .map(|v| match v {
1784 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
1785 value: *value * -1.0,
1786 ty: *ty,
1787 meta: meta.clone(),
1788 }),
1789 _ => Err(err()),
1790 })
1791 .collect::<Result<Vec<_>, _>>()?;
1792
1793 KclValue::Tuple {
1794 value: values,
1795 meta: meta.clone(),
1796 }
1797 }
1798 KclValue::HomArray {
1799 value: values,
1800 ty: ty @ RuntimeType::Primitive(PrimitiveType::Number(_)),
1801 } => {
1802 let values = values
1803 .iter()
1804 .map(|v| match v {
1805 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
1806 value: *value * -1.0,
1807 ty: *ty,
1808 meta: meta.clone(),
1809 }),
1810 _ => Err(err()),
1811 })
1812 .collect::<Result<Vec<_>, _>>()?;
1813
1814 KclValue::HomArray {
1815 value: values,
1816 ty: ty.clone(),
1817 }
1818 }
1819 _ => return Err(err()),
1820 };
1821
1822 let mut value = values.clone();
1823 value.insert("direction".to_owned(), direction);
1824 Ok(KclValue::Object {
1825 value,
1826 meta: meta.clone(),
1827 constrainable: false,
1828 })
1829 }
1830 _ => Err(err()),
1831 }
1832 }
1833}
1834
1835pub(crate) async fn execute_pipe_body(
1836 exec_state: &mut ExecState,
1837 body: &[Expr],
1838 source_range: SourceRange,
1839 ctx: &ExecutorContext,
1840) -> Result<KclValue, KclError> {
1841 let Some((first, body)) = body.split_first() else {
1842 return Err(KclError::new_semantic(KclErrorDetails::new(
1843 "Pipe expressions cannot be empty".to_owned(),
1844 vec![source_range],
1845 )));
1846 };
1847 let meta = Metadata {
1852 source_range: SourceRange::from(first),
1853 };
1854 let output = ctx
1855 .execute_expr(first, exec_state, &meta, &[], StatementKind::Expression)
1856 .await?;
1857
1858 let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
1862 let result = inner_execute_pipe_body(exec_state, body, ctx).await;
1864 exec_state.mod_local.pipe_value = previous_pipe_value;
1866
1867 result
1868}
1869
1870#[async_recursion]
1873async fn inner_execute_pipe_body(
1874 exec_state: &mut ExecState,
1875 body: &[Expr],
1876 ctx: &ExecutorContext,
1877) -> Result<KclValue, KclError> {
1878 for expression in body {
1879 if let Expr::TagDeclarator(_) = expression {
1880 return Err(KclError::new_semantic(KclErrorDetails::new(
1881 format!("This cannot be in a PipeExpression: {expression:?}"),
1882 vec![expression.into()],
1883 )));
1884 }
1885 let metadata = Metadata {
1886 source_range: SourceRange::from(expression),
1887 };
1888 let output = ctx
1889 .execute_expr(expression, exec_state, &metadata, &[], StatementKind::Expression)
1890 .await?;
1891 exec_state.mod_local.pipe_value = Some(output);
1892 }
1893 let final_output = exec_state.mod_local.pipe_value.take().unwrap();
1895 Ok(final_output)
1896}
1897
1898impl Node<TagDeclarator> {
1899 pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
1900 let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
1901 value: self.name.clone(),
1902 info: Vec::new(),
1903 meta: vec![Metadata {
1904 source_range: self.into(),
1905 }],
1906 }));
1907
1908 exec_state
1909 .mut_stack()
1910 .add(self.name.clone(), memory_item.clone(), self.into())?;
1911
1912 Ok(self.into())
1913 }
1914}
1915
1916impl Node<ArrayExpression> {
1917 #[async_recursion]
1918 pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1919 let mut results = Vec::with_capacity(self.elements.len());
1920
1921 for element in &self.elements {
1922 let metadata = Metadata::from(element);
1923 let value = ctx
1926 .execute_expr(element, exec_state, &metadata, &[], StatementKind::Expression)
1927 .await?;
1928
1929 results.push(value);
1930 }
1931
1932 Ok(KclValue::HomArray {
1933 value: results,
1934 ty: RuntimeType::Primitive(PrimitiveType::Any),
1935 })
1936 }
1937}
1938
1939impl Node<ArrayRangeExpression> {
1940 #[async_recursion]
1941 pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1942 let metadata = Metadata::from(&self.start_element);
1943 let start_val = ctx
1944 .execute_expr(
1945 &self.start_element,
1946 exec_state,
1947 &metadata,
1948 &[],
1949 StatementKind::Expression,
1950 )
1951 .await?;
1952 let (start, start_ty) = start_val
1953 .as_int_with_ty()
1954 .ok_or(KclError::new_semantic(KclErrorDetails::new(
1955 format!("Expected int but found {}", start_val.human_friendly_type()),
1956 vec![self.into()],
1957 )))?;
1958 let metadata = Metadata::from(&self.end_element);
1959 let end_val = ctx
1960 .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
1961 .await?;
1962 let (end, end_ty) = end_val
1963 .as_int_with_ty()
1964 .ok_or(KclError::new_semantic(KclErrorDetails::new(
1965 format!("Expected int but found {}", end_val.human_friendly_type()),
1966 vec![self.into()],
1967 )))?;
1968
1969 if start_ty != end_ty {
1970 let start = start_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: start_ty });
1971 let start = fmt::human_display_number(start.n, start.ty);
1972 let end = end_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: end_ty });
1973 let end = fmt::human_display_number(end.n, end.ty);
1974 return Err(KclError::new_semantic(KclErrorDetails::new(
1975 format!("Range start and end must be of the same type, but found {start} and {end}"),
1976 vec![self.into()],
1977 )));
1978 }
1979
1980 if end < start {
1981 return Err(KclError::new_semantic(KclErrorDetails::new(
1982 format!("Range start is greater than range end: {start} .. {end}"),
1983 vec![self.into()],
1984 )));
1985 }
1986
1987 let range: Vec<_> = if self.end_inclusive {
1988 (start..=end).collect()
1989 } else {
1990 (start..end).collect()
1991 };
1992
1993 let meta = vec![Metadata {
1994 source_range: self.into(),
1995 }];
1996
1997 Ok(KclValue::HomArray {
1998 value: range
1999 .into_iter()
2000 .map(|num| KclValue::Number {
2001 value: num as f64,
2002 ty: start_ty,
2003 meta: meta.clone(),
2004 })
2005 .collect(),
2006 ty: RuntimeType::Primitive(PrimitiveType::Number(start_ty)),
2007 })
2008 }
2009}
2010
2011impl Node<ObjectExpression> {
2012 #[async_recursion]
2013 pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
2014 let mut object = HashMap::with_capacity(self.properties.len());
2015 for property in &self.properties {
2016 let metadata = Metadata::from(&property.value);
2017 let result = ctx
2018 .execute_expr(&property.value, exec_state, &metadata, &[], StatementKind::Expression)
2019 .await?;
2020
2021 object.insert(property.key.name.clone(), result);
2022 }
2023
2024 Ok(KclValue::Object {
2025 value: object,
2026 meta: vec![Metadata {
2027 source_range: self.into(),
2028 }],
2029 constrainable: false,
2030 })
2031 }
2032}
2033
2034fn article_for<S: AsRef<str>>(s: S) -> &'static str {
2035 if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
2037 "an"
2038 } else {
2039 "a"
2040 }
2041}
2042
2043fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
2044 v.as_ty_f64().ok_or_else(|| {
2045 let actual_type = v.human_friendly_type();
2046 KclError::new_semantic(KclErrorDetails::new(
2047 format!("Expected a number, but found {actual_type}",),
2048 vec![source_range],
2049 ))
2050 })
2051}
2052
2053impl Node<IfExpression> {
2054 #[async_recursion]
2055 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
2056 let cond = ctx
2058 .execute_expr(
2059 &self.cond,
2060 exec_state,
2061 &Metadata::from(self),
2062 &[],
2063 StatementKind::Expression,
2064 )
2065 .await?
2066 .get_bool()?;
2067 if cond {
2068 let block_result = ctx.exec_block(&*self.then_val, exec_state, BodyType::Block).await?;
2069 return Ok(block_result.unwrap());
2073 }
2074
2075 for else_if in &self.else_ifs {
2077 let cond = ctx
2078 .execute_expr(
2079 &else_if.cond,
2080 exec_state,
2081 &Metadata::from(self),
2082 &[],
2083 StatementKind::Expression,
2084 )
2085 .await?
2086 .get_bool()?;
2087 if cond {
2088 let block_result = ctx.exec_block(&*else_if.then_val, exec_state, BodyType::Block).await?;
2089 return Ok(block_result.unwrap());
2093 }
2094 }
2095
2096 ctx.exec_block(&*self.final_else, exec_state, BodyType::Block)
2098 .await
2099 .map(|expr| expr.unwrap())
2100 }
2101}
2102
2103#[derive(Debug)]
2104enum Property {
2105 UInt(usize),
2106 String(String),
2107}
2108
2109impl Property {
2110 #[allow(clippy::too_many_arguments)]
2111 async fn try_from<'a>(
2112 computed: bool,
2113 value: Expr,
2114 exec_state: &mut ExecState,
2115 sr: SourceRange,
2116 ctx: &ExecutorContext,
2117 metadata: &Metadata,
2118 annotations: &[Node<Annotation>],
2119 statement_kind: StatementKind<'a>,
2120 ) -> Result<Self, KclError> {
2121 let property_sr = vec![sr];
2122 if !computed {
2123 let Expr::Name(identifier) = value else {
2124 return Err(KclError::new_semantic(KclErrorDetails::new(
2126 "Object expressions like `obj.property` must use simple identifier names, not complex expressions"
2127 .to_owned(),
2128 property_sr,
2129 )));
2130 };
2131 return Ok(Property::String(identifier.to_string()));
2132 }
2133
2134 let prop_value = ctx
2135 .execute_expr(&value, exec_state, metadata, annotations, statement_kind)
2136 .await?;
2137 match prop_value {
2138 KclValue::Number { value, ty, meta: _ } => {
2139 if !matches!(
2140 ty,
2141 NumericType::Unknown
2142 | NumericType::Default { .. }
2143 | NumericType::Known(crate::exec::UnitType::Count)
2144 ) {
2145 return Err(KclError::new_semantic(KclErrorDetails::new(
2146 format!(
2147 "{value} is not a valid index, indices must be non-dimensional numbers. If you're sure this is correct, you can add `: number(Count)` to tell KCL this number is an index"
2148 ),
2149 property_sr,
2150 )));
2151 }
2152 if let Some(x) = crate::try_f64_to_usize(value) {
2153 Ok(Property::UInt(x))
2154 } else {
2155 Err(KclError::new_semantic(KclErrorDetails::new(
2156 format!("{value} is not a valid index, indices must be whole numbers >= 0"),
2157 property_sr,
2158 )))
2159 }
2160 }
2161 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2162 "Only numbers (>= 0) can be indexes".to_owned(),
2163 vec![sr],
2164 ))),
2165 }
2166 }
2167}
2168
2169impl Property {
2170 fn type_name(&self) -> &'static str {
2171 match self {
2172 Property::UInt(_) => "number",
2173 Property::String(_) => "string",
2174 }
2175 }
2176}
2177
2178impl Node<PipeExpression> {
2179 #[async_recursion]
2180 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
2181 execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
2182 }
2183}
2184
2185#[cfg(test)]
2186mod test {
2187 use std::sync::Arc;
2188
2189 use tokio::io::AsyncWriteExt;
2190
2191 use super::*;
2192 use crate::{
2193 ExecutorSettings,
2194 errors::Severity,
2195 exec::UnitType,
2196 execution::{ContextType, parse_execute},
2197 };
2198
2199 #[tokio::test(flavor = "multi_thread")]
2200 async fn ascription() {
2201 let program = r#"
2202a = 42: number
2203b = a: number
2204p = {
2205 origin = { x = 0, y = 0, z = 0 },
2206 xAxis = { x = 1, y = 0, z = 0 },
2207 yAxis = { x = 0, y = 1, z = 0 },
2208 zAxis = { x = 0, y = 0, z = 1 }
2209}: Plane
2210arr1 = [42]: [number(cm)]
2211"#;
2212
2213 let result = parse_execute(program).await.unwrap();
2214 let mem = result.exec_state.stack();
2215 assert!(matches!(
2216 mem.memory
2217 .get_from("p", result.mem_env, SourceRange::default(), 0)
2218 .unwrap(),
2219 KclValue::Plane { .. }
2220 ));
2221 let arr1 = mem
2222 .memory
2223 .get_from("arr1", result.mem_env, SourceRange::default(), 0)
2224 .unwrap();
2225 if let KclValue::HomArray { value, ty } = arr1 {
2226 assert_eq!(value.len(), 1, "Expected Vec with specific length: found {value:?}");
2227 assert_eq!(
2228 *ty,
2229 RuntimeType::known_length(kittycad_modeling_cmds::units::UnitLength::Centimeters)
2230 );
2231 if let KclValue::Number { value, ty, .. } = &value[0] {
2233 assert_eq!(*value, 42.0);
2235 assert_eq!(
2236 *ty,
2237 NumericType::Known(UnitType::Length(kittycad_modeling_cmds::units::UnitLength::Centimeters))
2238 );
2239 } else {
2240 panic!("Expected a number; found {:?}", value[0]);
2241 }
2242 } else {
2243 panic!("Expected HomArray; found {arr1:?}");
2244 }
2245
2246 let program = r#"
2247a = 42: string
2248"#;
2249 let result = parse_execute(program).await;
2250 let err = result.unwrap_err();
2251 assert!(
2252 err.to_string()
2253 .contains("could not coerce a number (with type `number`) to type `string`"),
2254 "Expected error but found {err:?}"
2255 );
2256
2257 let program = r#"
2258a = 42: Plane
2259"#;
2260 let result = parse_execute(program).await;
2261 let err = result.unwrap_err();
2262 assert!(
2263 err.to_string()
2264 .contains("could not coerce a number (with type `number`) to type `Plane`"),
2265 "Expected error but found {err:?}"
2266 );
2267
2268 let program = r#"
2269arr = [0]: [string]
2270"#;
2271 let result = parse_execute(program).await;
2272 let err = result.unwrap_err();
2273 assert!(
2274 err.to_string().contains(
2275 "could not coerce an array of `number` with 1 value (with type `[any; 1]`) to type `[string]`"
2276 ),
2277 "Expected error but found {err:?}"
2278 );
2279
2280 let program = r#"
2281mixedArr = [0, "a"]: [number(mm)]
2282"#;
2283 let result = parse_execute(program).await;
2284 let err = result.unwrap_err();
2285 assert!(
2286 err.to_string().contains(
2287 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
2288 ),
2289 "Expected error but found {err:?}"
2290 );
2291
2292 let program = r#"
2293mixedArr = [0, "a"]: [mm]
2294"#;
2295 let result = parse_execute(program).await;
2296 let err = result.unwrap_err();
2297 assert!(
2298 err.to_string().contains(
2299 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
2300 ),
2301 "Expected error but found {err:?}"
2302 );
2303 }
2304
2305 #[tokio::test(flavor = "multi_thread")]
2306 async fn neg_plane() {
2307 let program = r#"
2308p = {
2309 origin = { x = 0, y = 0, z = 0 },
2310 xAxis = { x = 1, y = 0, z = 0 },
2311 yAxis = { x = 0, y = 1, z = 0 },
2312}: Plane
2313p2 = -p
2314"#;
2315
2316 let result = parse_execute(program).await.unwrap();
2317 let mem = result.exec_state.stack();
2318 match mem
2319 .memory
2320 .get_from("p2", result.mem_env, SourceRange::default(), 0)
2321 .unwrap()
2322 {
2323 KclValue::Plane { value } => {
2324 assert_eq!(value.info.x_axis.x, -1.0);
2325 assert_eq!(value.info.x_axis.y, 0.0);
2326 assert_eq!(value.info.x_axis.z, 0.0);
2327 }
2328 _ => unreachable!(),
2329 }
2330 }
2331
2332 #[tokio::test(flavor = "multi_thread")]
2333 async fn multiple_returns() {
2334 let program = r#"fn foo() {
2335 return 0
2336 return 42
2337}
2338
2339a = foo()
2340"#;
2341
2342 let result = parse_execute(program).await;
2343 assert!(result.unwrap_err().to_string().contains("return"));
2344 }
2345
2346 #[tokio::test(flavor = "multi_thread")]
2347 async fn load_all_modules() {
2348 let program_a_kcl = r#"
2350export a = 1
2351"#;
2352 let program_b_kcl = r#"
2354import a from 'a.kcl'
2355
2356export b = a + 1
2357"#;
2358 let program_c_kcl = r#"
2360import a from 'a.kcl'
2361
2362export c = a + 2
2363"#;
2364
2365 let main_kcl = r#"
2367import b from 'b.kcl'
2368import c from 'c.kcl'
2369
2370d = b + c
2371"#;
2372
2373 let main = crate::parsing::parse_str(main_kcl, ModuleId::default())
2374 .parse_errs_as_err()
2375 .unwrap();
2376
2377 let tmpdir = tempfile::TempDir::with_prefix("zma_kcl_load_all_modules").unwrap();
2378
2379 tokio::fs::File::create(tmpdir.path().join("main.kcl"))
2380 .await
2381 .unwrap()
2382 .write_all(main_kcl.as_bytes())
2383 .await
2384 .unwrap();
2385
2386 tokio::fs::File::create(tmpdir.path().join("a.kcl"))
2387 .await
2388 .unwrap()
2389 .write_all(program_a_kcl.as_bytes())
2390 .await
2391 .unwrap();
2392
2393 tokio::fs::File::create(tmpdir.path().join("b.kcl"))
2394 .await
2395 .unwrap()
2396 .write_all(program_b_kcl.as_bytes())
2397 .await
2398 .unwrap();
2399
2400 tokio::fs::File::create(tmpdir.path().join("c.kcl"))
2401 .await
2402 .unwrap()
2403 .write_all(program_c_kcl.as_bytes())
2404 .await
2405 .unwrap();
2406
2407 let exec_ctxt = ExecutorContext {
2408 engine: Arc::new(Box::new(
2409 crate::engine::conn_mock::EngineConnection::new()
2410 .map_err(|err| {
2411 KclError::new_internal(KclErrorDetails::new(
2412 format!("Failed to create mock engine connection: {err}"),
2413 vec![SourceRange::default()],
2414 ))
2415 })
2416 .unwrap(),
2417 )),
2418 fs: Arc::new(crate::fs::FileManager::new()),
2419 settings: ExecutorSettings {
2420 project_directory: Some(crate::TypedPath(tmpdir.path().into())),
2421 ..Default::default()
2422 },
2423 context_type: ContextType::Mock,
2424 };
2425 let mut exec_state = ExecState::new(&exec_ctxt);
2426
2427 exec_ctxt
2428 .run(
2429 &crate::Program {
2430 ast: main.clone(),
2431 original_file_contents: "".to_owned(),
2432 },
2433 &mut exec_state,
2434 )
2435 .await
2436 .unwrap();
2437 }
2438
2439 #[tokio::test(flavor = "multi_thread")]
2440 async fn user_coercion() {
2441 let program = r#"fn foo(x: Axis2d) {
2442 return 0
2443}
2444
2445foo(x = { direction = [0, 0], origin = [0, 0]})
2446"#;
2447
2448 parse_execute(program).await.unwrap();
2449
2450 let program = r#"fn foo(x: Axis3d) {
2451 return 0
2452}
2453
2454foo(x = { direction = [0, 0], origin = [0, 0]})
2455"#;
2456
2457 parse_execute(program).await.unwrap_err();
2458 }
2459
2460 #[tokio::test(flavor = "multi_thread")]
2461 async fn coerce_return() {
2462 let program = r#"fn foo(): number(mm) {
2463 return 42
2464}
2465
2466a = foo()
2467"#;
2468
2469 parse_execute(program).await.unwrap();
2470
2471 let program = r#"fn foo(): mm {
2472 return 42
2473}
2474
2475a = foo()
2476"#;
2477
2478 parse_execute(program).await.unwrap();
2479
2480 let program = r#"fn foo(): number(mm) {
2481 return { bar: 42 }
2482}
2483
2484a = foo()
2485"#;
2486
2487 parse_execute(program).await.unwrap_err();
2488
2489 let program = r#"fn foo(): mm {
2490 return { bar: 42 }
2491}
2492
2493a = foo()
2494"#;
2495
2496 parse_execute(program).await.unwrap_err();
2497 }
2498
2499 #[tokio::test(flavor = "multi_thread")]
2500 async fn test_sensible_error_when_missing_equals_in_kwarg() {
2501 for (i, call) in ["f(x=1,3,0)", "f(x=1,3,z)", "f(x=1,0,z=1)", "f(x=1, 3 + 4, z)"]
2502 .into_iter()
2503 .enumerate()
2504 {
2505 let program = format!(
2506 "fn foo() {{ return 0 }}
2507z = 0
2508fn f(x, y, z) {{ return 0 }}
2509{call}"
2510 );
2511 let err = parse_execute(&program).await.unwrap_err();
2512 let msg = err.message();
2513 assert!(
2514 msg.contains("This argument needs a label, but it doesn't have one"),
2515 "failed test {i}: {msg}"
2516 );
2517 assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
2518 if i == 0 {
2519 assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
2520 }
2521 }
2522 }
2523
2524 #[tokio::test(flavor = "multi_thread")]
2525 async fn default_param_for_unlabeled() {
2526 let ast = r#"fn myExtrude(@sk, length) {
2529 return extrude(sk, length)
2530}
2531sketch001 = startSketchOn(XY)
2532 |> circle(center = [0, 0], radius = 93.75)
2533 |> myExtrude(length = 40)
2534"#;
2535
2536 parse_execute(ast).await.unwrap();
2537 }
2538
2539 #[tokio::test(flavor = "multi_thread")]
2540 async fn dont_use_unlabelled_as_input() {
2541 let ast = r#"length = 10
2543startSketchOn(XY)
2544 |> circle(center = [0, 0], radius = 93.75)
2545 |> extrude(length)
2546"#;
2547
2548 parse_execute(ast).await.unwrap();
2549 }
2550
2551 #[tokio::test(flavor = "multi_thread")]
2552 async fn ascription_in_binop() {
2553 let ast = r#"foo = tan(0): number(rad) - 4deg"#;
2554 parse_execute(ast).await.unwrap();
2555
2556 let ast = r#"foo = tan(0): rad - 4deg"#;
2557 parse_execute(ast).await.unwrap();
2558 }
2559
2560 #[tokio::test(flavor = "multi_thread")]
2561 async fn neg_sqrt() {
2562 let ast = r#"bad = sqrt(-2)"#;
2563
2564 let e = parse_execute(ast).await.unwrap_err();
2565 assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
2567 }
2568
2569 #[tokio::test(flavor = "multi_thread")]
2570 async fn non_array_fns() {
2571 let ast = r#"push(1, item = 2)
2572pop(1)
2573map(1, f = fn(@x) { return x + 1 })
2574reduce(1, f = fn(@x, accum) { return accum + x}, initial = 0)"#;
2575
2576 parse_execute(ast).await.unwrap();
2577 }
2578
2579 #[tokio::test(flavor = "multi_thread")]
2580 async fn non_array_indexing() {
2581 let good = r#"a = 42
2582good = a[0]
2583"#;
2584 let result = parse_execute(good).await.unwrap();
2585 let mem = result.exec_state.stack();
2586 let num = mem
2587 .memory
2588 .get_from("good", result.mem_env, SourceRange::default(), 0)
2589 .unwrap()
2590 .as_ty_f64()
2591 .unwrap();
2592 assert_eq!(num.n, 42.0);
2593
2594 let bad = r#"a = 42
2595bad = a[1]
2596"#;
2597
2598 parse_execute(bad).await.unwrap_err();
2599 }
2600
2601 #[tokio::test(flavor = "multi_thread")]
2602 async fn coerce_unknown_to_length() {
2603 let ast = r#"x = 2mm * 2mm
2604y = x: number(Length)"#;
2605 let e = parse_execute(ast).await.unwrap_err();
2606 assert!(
2607 e.message().contains("could not coerce"),
2608 "Error message: '{}'",
2609 e.message()
2610 );
2611
2612 let ast = r#"x = 2mm
2613y = x: number(Length)"#;
2614 let result = parse_execute(ast).await.unwrap();
2615 let mem = result.exec_state.stack();
2616 let num = mem
2617 .memory
2618 .get_from("y", result.mem_env, SourceRange::default(), 0)
2619 .unwrap()
2620 .as_ty_f64()
2621 .unwrap();
2622 assert_eq!(num.n, 2.0);
2623 assert_eq!(num.ty, NumericType::mm());
2624 }
2625
2626 #[tokio::test(flavor = "multi_thread")]
2627 async fn one_warning_unknown() {
2628 let ast = r#"
2629// Should warn once
2630a = PI * 2
2631// Should warn once
2632b = (PI * 2) / 3
2633// Should not warn
2634c = ((PI * 2) / 3): number(deg)
2635"#;
2636
2637 let result = parse_execute(ast).await.unwrap();
2638 assert_eq!(result.exec_state.errors().len(), 2);
2639 }
2640
2641 #[tokio::test(flavor = "multi_thread")]
2642 async fn non_count_indexing() {
2643 let ast = r#"x = [0, 0]
2644y = x[1mm]
2645"#;
2646 parse_execute(ast).await.unwrap_err();
2647
2648 let ast = r#"x = [0, 0]
2649y = 1deg
2650z = x[y]
2651"#;
2652 parse_execute(ast).await.unwrap_err();
2653
2654 let ast = r#"x = [0, 0]
2655y = x[0mm + 1]
2656"#;
2657 parse_execute(ast).await.unwrap_err();
2658 }
2659
2660 #[tokio::test(flavor = "multi_thread")]
2661 async fn getting_property_of_plane() {
2662 let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap();
2664
2665 parse_execute(&ast).await.unwrap();
2666 }
2667
2668 #[tokio::test(flavor = "multi_thread")]
2669 async fn custom_warning() {
2670 let warn = r#"
2671a = PI * 2
2672"#;
2673 let result = parse_execute(warn).await.unwrap();
2674 assert_eq!(result.exec_state.errors().len(), 1);
2675 assert_eq!(result.exec_state.errors()[0].severity, Severity::Warning);
2676
2677 let allow = r#"
2678@warnings(allow = unknownUnits)
2679a = PI * 2
2680"#;
2681 let result = parse_execute(allow).await.unwrap();
2682 assert_eq!(result.exec_state.errors().len(), 0);
2683
2684 let deny = r#"
2685@warnings(deny = [unknownUnits])
2686a = PI * 2
2687"#;
2688 let result = parse_execute(deny).await.unwrap();
2689 assert_eq!(result.exec_state.errors().len(), 1);
2690 assert_eq!(result.exec_state.errors()[0].severity, Severity::Error);
2691 }
2692}