1use std::collections::HashMap;
2
3use async_recursion::async_recursion;
4
5use crate::{
6 errors::{KclError, KclErrorDetails},
7 execution::{
8 annotations,
9 fn_call::Args,
10 kcl_value::{FunctionSource, TypeDef},
11 memory,
12 state::ModuleState,
13 types::{NumericType, PrimitiveType, RuntimeType},
14 BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, PlaneType, StatementKind,
15 TagIdentifier,
16 },
17 fmt,
18 modules::{ModuleId, ModulePath, ModuleRepr},
19 parsing::ast::types::{
20 Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
21 BinaryPart, BodyItem, Expr, IfExpression, ImportPath, ImportSelector, ItemVisibility, LiteralIdentifier,
22 LiteralValue, MemberExpression, MemberObject, Name, Node, NodeRef, ObjectExpression, PipeExpression, Program,
23 TagDeclarator, Type, UnaryExpression, UnaryOperator,
24 },
25 source_range::SourceRange,
26 std::args::TyF64,
27 CompilationError,
28};
29
30impl<'a> StatementKind<'a> {
31 fn expect_name(&self) -> &'a str {
32 match self {
33 StatementKind::Declaration { name } => name,
34 StatementKind::Expression => unreachable!(),
35 }
36 }
37}
38
39impl ExecutorContext {
40 async fn handle_annotations(
42 &self,
43 annotations: impl Iterator<Item = &Node<Annotation>>,
44 body_type: BodyType,
45 exec_state: &mut ExecState,
46 ) -> Result<bool, KclError> {
47 let mut no_prelude = false;
48 for annotation in annotations {
49 if annotation.name() == Some(annotations::SETTINGS) {
50 if matches!(body_type, BodyType::Root) {
51 if exec_state.mod_local.settings.update_from_annotation(annotation)? {
52 exec_state.mod_local.explicit_length_units = true;
53 }
54 } else {
55 exec_state.err(CompilationError::err(
56 annotation.as_source_range(),
57 "Settings can only be modified at the top level scope of a file",
58 ));
59 }
60 } else if annotation.name() == Some(annotations::NO_PRELUDE) {
61 if matches!(body_type, BodyType::Root) {
62 no_prelude = true;
63 } else {
64 exec_state.err(CompilationError::err(
65 annotation.as_source_range(),
66 "The standard library can only be skipped at the top level scope of a file",
67 ));
68 }
69 } else {
70 exec_state.warn(CompilationError::err(
71 annotation.as_source_range(),
72 "Unknown annotation",
73 ));
74 }
75 }
76 Ok(no_prelude)
77 }
78
79 pub(super) async fn exec_module_body(
80 &self,
81 program: &Node<Program>,
82 exec_state: &mut ExecState,
83 preserve_mem: bool,
84 module_id: ModuleId,
85 path: &ModulePath,
86 ) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError> {
87 crate::log::log(format!("enter module {path} {}", exec_state.stack()));
88
89 let mut local_state = ModuleState::new(path.std_path(), exec_state.stack().memory.clone(), Some(module_id));
90 if !preserve_mem {
91 std::mem::swap(&mut exec_state.mod_local, &mut local_state);
92 }
93
94 let no_prelude = self
95 .handle_annotations(program.inner_attrs.iter(), crate::execution::BodyType::Root, exec_state)
96 .await?;
97
98 if !preserve_mem {
99 exec_state.mut_stack().push_new_root_env(!no_prelude);
100 }
101
102 let result = self
103 .exec_block(program, exec_state, crate::execution::BodyType::Root)
104 .await;
105
106 let env_ref = if preserve_mem {
107 exec_state.mut_stack().pop_and_preserve_env()
108 } else {
109 exec_state.mut_stack().pop_env()
110 };
111 if !preserve_mem {
112 std::mem::swap(&mut exec_state.mod_local, &mut local_state);
113 }
114
115 crate::log::log(format!("leave {path}"));
116
117 result.map(|result| (result, env_ref, local_state.module_exports))
118 }
119
120 #[async_recursion]
122 pub(super) async fn exec_block<'a>(
123 &'a self,
124 program: NodeRef<'a, Program>,
125 exec_state: &mut ExecState,
126 body_type: BodyType,
127 ) -> Result<Option<KclValue>, KclError> {
128 let mut last_expr = None;
129 for statement in &program.body {
131 match statement {
132 BodyItem::ImportStatement(import_stmt) => {
133 if !matches!(body_type, BodyType::Root) {
134 return Err(KclError::Semantic(KclErrorDetails::new(
135 "Imports are only supported at the top-level of a file.".to_owned(),
136 vec![import_stmt.into()],
137 )));
138 }
139
140 let source_range = SourceRange::from(import_stmt);
141 let attrs = &import_stmt.outer_attrs;
142 let module_id = self
143 .open_module(&import_stmt.path, attrs, exec_state, source_range)
144 .await?;
145
146 match &import_stmt.selector {
147 ImportSelector::List { items } => {
148 let (env_ref, module_exports) =
149 self.exec_module_for_items(module_id, exec_state, source_range).await?;
150 for import_item in items {
151 let mem = &exec_state.stack().memory;
153 let mut value = mem
154 .get_from(&import_item.name.name, env_ref, import_item.into(), 0)
155 .cloned();
156 let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.name.name);
157 let mut ty = mem.get_from(&ty_name, env_ref, import_item.into(), 0).cloned();
158
159 if value.is_err() && ty.is_err() {
160 return Err(KclError::UndefinedValue(KclErrorDetails::new(
161 format!("{} is not defined in module", import_item.name.name),
162 vec![SourceRange::from(&import_item.name)],
163 )));
164 }
165
166 if value.is_ok() && !module_exports.contains(&import_item.name.name) {
168 value = Err(KclError::Semantic(KclErrorDetails::new(
169 format!(
170 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
171 import_item.name.name
172 ),
173 vec![SourceRange::from(&import_item.name)],
174 )));
175 }
176
177 if ty.is_ok() && !module_exports.contains(&ty_name) {
178 ty = Err(KclError::Semantic(KclErrorDetails::new(String::new(), vec![])));
179 }
180
181 if value.is_err() && ty.is_err() {
182 return value.map(Option::Some);
183 }
184
185 if let Ok(value) = value {
187 exec_state.mut_stack().add(
188 import_item.identifier().to_owned(),
189 value,
190 SourceRange::from(&import_item.name),
191 )?;
192
193 if let ItemVisibility::Export = import_stmt.visibility {
194 exec_state
195 .mod_local
196 .module_exports
197 .push(import_item.identifier().to_owned());
198 }
199 }
200
201 if let Ok(ty) = ty {
202 let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.identifier());
203 exec_state.mut_stack().add(
205 ty_name.clone(),
206 ty,
207 SourceRange::from(&import_item.name),
208 )?;
209
210 if let ItemVisibility::Export = import_stmt.visibility {
211 exec_state.mod_local.module_exports.push(ty_name);
212 }
213 }
214 }
215 }
216 ImportSelector::Glob(_) => {
217 let (env_ref, module_exports) =
218 self.exec_module_for_items(module_id, exec_state, source_range).await?;
219 for name in module_exports.iter() {
220 let item = exec_state
221 .stack()
222 .memory
223 .get_from(name, env_ref, source_range, 0)
224 .map_err(|_err| {
225 KclError::Internal(KclErrorDetails::new(
226 format!("{} is not defined in module (but was exported?)", name),
227 vec![source_range],
228 ))
229 })?
230 .clone();
231 exec_state.mut_stack().add(name.to_owned(), item, source_range)?;
232
233 if let ItemVisibility::Export = import_stmt.visibility {
234 exec_state.mod_local.module_exports.push(name.clone());
235 }
236 }
237 }
238 ImportSelector::None { .. } => {
239 let name = import_stmt.module_name().unwrap();
240 let item = KclValue::Module {
241 value: module_id,
242 meta: vec![source_range.into()],
243 };
244 exec_state.mut_stack().add(name, item, source_range)?;
245 }
246 }
247 last_expr = None;
248 }
249 BodyItem::ExpressionStatement(expression_statement) => {
250 let metadata = Metadata::from(expression_statement);
251 last_expr = Some(
252 self.execute_expr(
253 &expression_statement.expression,
254 exec_state,
255 &metadata,
256 &[],
257 StatementKind::Expression,
258 )
259 .await?,
260 );
261 }
262 BodyItem::VariableDeclaration(variable_declaration) => {
263 let var_name = variable_declaration.declaration.id.name.to_string();
264 let source_range = SourceRange::from(&variable_declaration.declaration.init);
265 let metadata = Metadata { source_range };
266
267 let annotations = &variable_declaration.outer_attrs;
268
269 let value = self
270 .execute_expr(
271 &variable_declaration.declaration.init,
272 exec_state,
273 &metadata,
274 annotations,
275 StatementKind::Declaration { name: &var_name },
276 )
277 .await?;
278 exec_state
279 .mut_stack()
280 .add(var_name.clone(), value.clone(), source_range)?;
281
282 if let ItemVisibility::Export = variable_declaration.visibility {
284 exec_state.mod_local.module_exports.push(var_name);
285 }
286 last_expr = matches!(body_type, BodyType::Root).then_some(value);
288 }
289 BodyItem::TypeDeclaration(ty) => {
290 let metadata = Metadata::from(&**ty);
291 let impl_kind = annotations::get_impl(&ty.outer_attrs, metadata.source_range)?.unwrap_or_default();
292 match impl_kind {
293 annotations::Impl::Rust => {
294 let std_path = match &exec_state.mod_local.std_path {
295 Some(p) => p,
296 None => {
297 return Err(KclError::Semantic(KclErrorDetails::new(
298 "User-defined types are not yet supported.".to_owned(),
299 vec![metadata.source_range],
300 )));
301 }
302 };
303 let (t, props) = crate::std::std_ty(std_path, &ty.name.name);
304 let value = KclValue::Type {
305 value: TypeDef::RustRepr(t, props),
306 meta: vec![metadata],
307 };
308 let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
309 exec_state
310 .mut_stack()
311 .add(name_in_mem.clone(), value, metadata.source_range)
312 .map_err(|_| {
313 KclError::Semantic(KclErrorDetails::new(
314 format!("Redefinition of type {}.", ty.name.name),
315 vec![metadata.source_range],
316 ))
317 })?;
318
319 if let ItemVisibility::Export = ty.visibility {
320 exec_state.mod_local.module_exports.push(name_in_mem);
321 }
322 }
323 annotations::Impl::Primitive => {}
325 annotations::Impl::Kcl => match &ty.alias {
326 Some(alias) => {
327 let value = KclValue::Type {
328 value: TypeDef::Alias(
329 RuntimeType::from_parsed(
330 alias.inner.clone(),
331 exec_state,
332 metadata.source_range,
333 )
334 .map_err(|e| KclError::Semantic(e.into()))?,
335 ),
336 meta: vec![metadata],
337 };
338 let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
339 exec_state
340 .mut_stack()
341 .add(name_in_mem.clone(), value, metadata.source_range)
342 .map_err(|_| {
343 KclError::Semantic(KclErrorDetails::new(
344 format!("Redefinition of type {}.", ty.name.name),
345 vec![metadata.source_range],
346 ))
347 })?;
348
349 if let ItemVisibility::Export = ty.visibility {
350 exec_state.mod_local.module_exports.push(name_in_mem);
351 }
352 }
353 None => {
354 return Err(KclError::Semantic(KclErrorDetails::new(
355 "User-defined types are not yet supported.".to_owned(),
356 vec![metadata.source_range],
357 )))
358 }
359 },
360 }
361
362 last_expr = None;
363 }
364 BodyItem::ReturnStatement(return_statement) => {
365 let metadata = Metadata::from(return_statement);
366
367 if matches!(body_type, BodyType::Root) {
368 return Err(KclError::Semantic(KclErrorDetails::new(
369 "Cannot return from outside a function.".to_owned(),
370 vec![metadata.source_range],
371 )));
372 }
373
374 let value = self
375 .execute_expr(
376 &return_statement.argument,
377 exec_state,
378 &metadata,
379 &[],
380 StatementKind::Expression,
381 )
382 .await?;
383 exec_state
384 .mut_stack()
385 .add(memory::RETURN_NAME.to_owned(), value, metadata.source_range)
386 .map_err(|_| {
387 KclError::Semantic(KclErrorDetails::new(
388 "Multiple returns from a single function.".to_owned(),
389 vec![metadata.source_range],
390 ))
391 })?;
392 last_expr = None;
393 }
394 }
395 }
396
397 if matches!(body_type, BodyType::Root) {
398 self.engine
400 .flush_batch(
401 true,
404 SourceRange::new(program.end, program.end, program.module_id),
405 )
406 .await?;
407 }
408
409 Ok(last_expr)
410 }
411
412 pub async fn open_module(
413 &self,
414 path: &ImportPath,
415 attrs: &[Node<Annotation>],
416 exec_state: &mut ExecState,
417 source_range: SourceRange,
418 ) -> Result<ModuleId, KclError> {
419 let resolved_path = ModulePath::from_import_path(path, &self.settings.project_directory);
420
421 match path {
422 ImportPath::Kcl { .. } => {
423 exec_state.global.mod_loader.cycle_check(&resolved_path, source_range)?;
424
425 if let Some(id) = exec_state.id_for_module(&resolved_path) {
426 return Ok(id);
427 }
428
429 let id = exec_state.next_module_id();
430 exec_state.add_path_to_source_id(resolved_path.clone(), id);
432 let source = resolved_path.source(&self.fs, source_range).await?;
433 exec_state.add_id_to_source(id, source.clone());
434 let parsed = crate::parsing::parse_str(&source.source, id).parse_errs_as_err()?;
436 exec_state.add_module(id, resolved_path, ModuleRepr::Kcl(parsed, None));
437
438 Ok(id)
439 }
440 ImportPath::Foreign { .. } => {
441 if let Some(id) = exec_state.id_for_module(&resolved_path) {
442 return Ok(id);
443 }
444
445 let id = exec_state.next_module_id();
446 let path = resolved_path.expect_path();
447 exec_state.add_path_to_source_id(resolved_path.clone(), id);
449 let format = super::import::format_from_annotations(attrs, path, source_range)?;
450 let geom = super::import::import_foreign(path, format, exec_state, self, source_range).await?;
451 exec_state.add_module(id, resolved_path, ModuleRepr::Foreign(geom, None));
452 Ok(id)
453 }
454 ImportPath::Std { .. } => {
455 if let Some(id) = exec_state.id_for_module(&resolved_path) {
456 return Ok(id);
457 }
458
459 let id = exec_state.next_module_id();
460 exec_state.add_path_to_source_id(resolved_path.clone(), id);
462 let source = resolved_path.source(&self.fs, source_range).await?;
463 exec_state.add_id_to_source(id, source.clone());
464 let parsed = crate::parsing::parse_str(&source.source, id)
465 .parse_errs_as_err()
466 .unwrap();
467 exec_state.add_module(id, resolved_path, ModuleRepr::Kcl(parsed, None));
468 Ok(id)
469 }
470 }
471 }
472
473 pub(super) async fn exec_module_for_items(
474 &self,
475 module_id: ModuleId,
476 exec_state: &mut ExecState,
477 source_range: SourceRange,
478 ) -> Result<(EnvironmentRef, Vec<String>), KclError> {
479 let path = exec_state.global.module_infos[&module_id].path.clone();
480 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
481 let result = match &mut repr {
484 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
485 ModuleRepr::Kcl(_, Some((_, env_ref, items))) => Ok((*env_ref, items.clone())),
486 ModuleRepr::Kcl(program, cache) => self
487 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, false)
488 .await
489 .map(|(val, er, items)| {
490 *cache = Some((val, er, items.clone()));
491 (er, items)
492 }),
493 ModuleRepr::Foreign(geom, _) => Err(KclError::Semantic(KclErrorDetails::new(
494 "Cannot import items from foreign modules".to_owned(),
495 vec![geom.source_range],
496 ))),
497 ModuleRepr::Dummy => unreachable!("Looking up {}, but it is still being interpreted", path),
498 };
499
500 exec_state.global.module_infos[&module_id].restore_repr(repr);
501 result
502 }
503
504 async fn exec_module_for_result(
505 &self,
506 module_id: ModuleId,
507 exec_state: &mut ExecState,
508 source_range: SourceRange,
509 ) -> Result<Option<KclValue>, KclError> {
510 let path = exec_state.global.module_infos[&module_id].path.clone();
511 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
512 let result = match &mut repr {
515 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
516 ModuleRepr::Kcl(_, Some((val, _, _))) => Ok(val.clone()),
517 ModuleRepr::Kcl(program, cached_items) => {
518 let result = self
519 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, false)
520 .await;
521 match result {
522 Ok((val, env, items)) => {
523 *cached_items = Some((val.clone(), env, items));
524 Ok(val)
525 }
526 Err(e) => Err(e),
527 }
528 }
529 ModuleRepr::Foreign(_, Some(imported)) => Ok(Some(imported.clone())),
530 ModuleRepr::Foreign(geom, cached) => {
531 let result = super::import::send_to_engine(geom.clone(), self)
532 .await
533 .map(|geom| Some(KclValue::ImportedGeometry(geom)));
534
535 match result {
536 Ok(val) => {
537 *cached = val.clone();
538 Ok(val)
539 }
540 Err(e) => Err(e),
541 }
542 }
543 ModuleRepr::Dummy => unreachable!(),
544 };
545
546 exec_state.global.module_infos[&module_id].restore_repr(repr);
547
548 result
549 }
550
551 pub async fn exec_module_from_ast(
552 &self,
553 program: &Node<Program>,
554 module_id: ModuleId,
555 path: &ModulePath,
556 exec_state: &mut ExecState,
557 source_range: SourceRange,
558 preserve_mem: bool,
559 ) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError> {
560 exec_state.global.mod_loader.enter_module(path);
561 let result = self
562 .exec_module_body(program, exec_state, preserve_mem, module_id, path)
563 .await;
564 exec_state.global.mod_loader.leave_module(path);
565
566 result.map_err(|err| {
567 if let KclError::ImportCycle(_) = err {
568 err.override_source_ranges(vec![source_range])
570 } else {
571 KclError::Semantic(KclErrorDetails::new(
573 format!(
574 "Error loading imported file ({path}). Open it to view more details.\n {}",
575 err.message()
576 ),
577 vec![source_range],
578 ))
579 }
580 })
581 }
582
583 #[async_recursion]
584 pub(super) async fn execute_expr<'a: 'async_recursion>(
585 &self,
586 init: &Expr,
587 exec_state: &mut ExecState,
588 metadata: &Metadata,
589 annotations: &[Node<Annotation>],
590 statement_kind: StatementKind<'a>,
591 ) -> Result<KclValue, KclError> {
592 let item = match init {
593 Expr::None(none) => KclValue::from(none),
594 Expr::Literal(literal) => KclValue::from_literal((**literal).clone(), exec_state),
595 Expr::TagDeclarator(tag) => tag.execute(exec_state).await?,
596 Expr::Name(name) => {
597 let value = name.get_result(exec_state, self).await?.clone();
598 if let KclValue::Module { value: module_id, meta } = value {
599 self.exec_module_for_result(
600 module_id,
601 exec_state,
602 metadata.source_range
603 ).await?
604 .unwrap_or_else(|| {
605 exec_state.warn(CompilationError::err(
606 metadata.source_range,
607 "Imported module has no return value. The last statement of the module must be an expression, usually the Solid.",
608 ));
609
610 let mut new_meta = vec![metadata.to_owned()];
611 new_meta.extend(meta);
612 KclValue::KclNone {
613 value: Default::default(),
614 meta: new_meta,
615 }
616 })
617 } else {
618 value
619 }
620 }
621 Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?,
622 Expr::FunctionExpression(function_expression) => {
623 let rust_impl = annotations::get_impl(annotations, metadata.source_range)?
624 .map(|s| s == annotations::Impl::Rust)
625 .unwrap_or(false);
626
627 if rust_impl {
628 if let Some(std_path) = &exec_state.mod_local.std_path {
629 let (func, props) = crate::std::std_fn(std_path, statement_kind.expect_name());
630 KclValue::Function {
631 value: FunctionSource::Std {
632 func,
633 props,
634 ast: function_expression.clone(),
635 },
636 meta: vec![metadata.to_owned()],
637 }
638 } else {
639 return Err(KclError::Semantic(KclErrorDetails::new(
640 "Rust implementation of functions is restricted to the standard library".to_owned(),
641 vec![metadata.source_range],
642 )));
643 }
644 } else {
645 KclValue::Function {
649 value: FunctionSource::User {
650 ast: function_expression.clone(),
651 settings: exec_state.mod_local.settings.clone(),
652 memory: exec_state.mut_stack().snapshot(),
653 },
654 meta: vec![metadata.to_owned()],
655 }
656 }
657 }
658 Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?,
659 Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?,
660 Expr::PipeSubstitution(pipe_substitution) => match statement_kind {
661 StatementKind::Declaration { name } => {
662 let message = format!(
663 "you cannot declare variable {name} as %, because % can only be used in function calls"
664 );
665
666 return Err(KclError::Semantic(KclErrorDetails::new(
667 message,
668 vec![pipe_substitution.into()],
669 )));
670 }
671 StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() {
672 Some(x) => x,
673 None => {
674 return Err(KclError::Semantic(KclErrorDetails::new(
675 "cannot use % outside a pipe expression".to_owned(),
676 vec![pipe_substitution.into()],
677 )));
678 }
679 },
680 },
681 Expr::ArrayExpression(array_expression) => array_expression.execute(exec_state, self).await?,
682 Expr::ArrayRangeExpression(range_expression) => range_expression.execute(exec_state, self).await?,
683 Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?,
684 Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state)?,
685 Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?,
686 Expr::IfExpression(expr) => expr.get_result(exec_state, self).await?,
687 Expr::LabelledExpression(expr) => {
688 let result = self
689 .execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind)
690 .await?;
691 exec_state
692 .mut_stack()
693 .add(expr.label.name.clone(), result.clone(), init.into())?;
694 result
696 }
697 Expr::AscribedExpression(expr) => expr.get_result(exec_state, self).await?,
698 };
699 Ok(item)
700 }
701}
702
703impl Node<AscribedExpression> {
704 #[async_recursion]
705 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
706 let metadata = Metadata {
707 source_range: SourceRange::from(self),
708 };
709 let result = ctx
710 .execute_expr(&self.expr, exec_state, &metadata, &[], StatementKind::Expression)
711 .await?;
712 apply_ascription(&result, &self.ty, exec_state, self.into())
713 }
714}
715
716fn apply_ascription(
717 value: &KclValue,
718 ty: &Node<Type>,
719 exec_state: &mut ExecState,
720 source_range: SourceRange,
721) -> Result<KclValue, KclError> {
722 let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into())
723 .map_err(|e| KclError::Semantic(e.into()))?;
724
725 let mut value = value.clone();
726
727 if let KclValue::Number { value: n, meta, .. } = &value {
730 if let RuntimeType::Primitive(PrimitiveType::Number(num)) = &ty {
731 if num.is_fully_specified() {
732 value = KclValue::Number {
733 ty: NumericType::Any,
734 value: *n,
735 meta: meta.clone(),
736 };
737 }
738 }
739 }
740
741 value.coerce(&ty, exec_state).map_err(|_| {
742 let suggestion = if ty == RuntimeType::length() {
743 ", you might try coercing to a fully specified numeric type such as `number(mm)`"
744 } else if ty == RuntimeType::angle() {
745 ", you might try coercing to a fully specified numeric type such as `number(deg)`"
746 } else {
747 ""
748 };
749 KclError::Semantic(KclErrorDetails::new(
750 format!(
751 "could not coerce {} value to type {ty}{suggestion}",
752 value.human_friendly_type()
753 ),
754 vec![source_range],
755 ))
756 })
757}
758
759impl BinaryPart {
760 #[async_recursion]
761 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
762 match self {
763 BinaryPart::Literal(literal) => Ok(KclValue::from_literal((**literal).clone(), exec_state)),
764 BinaryPart::Name(name) => name.get_result(exec_state, ctx).await.cloned(),
765 BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
766 BinaryPart::CallExpressionKw(call_expression) => call_expression.execute(exec_state, ctx).await,
767 BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
768 BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state),
769 BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
770 BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
771 }
772 }
773}
774
775impl Node<Name> {
776 pub(super) async fn get_result<'a>(
777 &self,
778 exec_state: &'a mut ExecState,
779 ctx: &ExecutorContext,
780 ) -> Result<&'a KclValue, KclError> {
781 if self.abs_path {
782 return Err(KclError::Semantic(KclErrorDetails::new(
783 "Absolute paths (names beginning with `::` are not yet supported)".to_owned(),
784 self.as_source_ranges(),
785 )));
786 }
787
788 if self.path.is_empty() {
789 return exec_state.stack().get(&self.name.name, self.into());
790 }
791
792 let mut mem_spec: Option<(EnvironmentRef, Vec<String>)> = None;
793 for p in &self.path {
794 let value = match mem_spec {
795 Some((env, exports)) => {
796 if !exports.contains(&p.name) {
797 return Err(KclError::Semantic(KclErrorDetails::new(
798 format!("Item {} not found in module's exported items", p.name),
799 p.as_source_ranges(),
800 )));
801 }
802
803 exec_state
804 .stack()
805 .memory
806 .get_from(&p.name, env, p.as_source_range(), 0)?
807 }
808 None => exec_state.stack().get(&p.name, self.into())?,
809 };
810
811 let KclValue::Module { value: module_id, .. } = value else {
812 return Err(KclError::Semantic(KclErrorDetails::new(
813 format!(
814 "Identifier in path must refer to a module, found {}",
815 value.human_friendly_type()
816 ),
817 p.as_source_ranges(),
818 )));
819 };
820
821 mem_spec = Some(
822 ctx.exec_module_for_items(*module_id, exec_state, p.as_source_range())
823 .await?,
824 );
825 }
826
827 let (env, exports) = mem_spec.unwrap();
828 if !exports.contains(&self.name.name) {
829 return Err(KclError::Semantic(KclErrorDetails::new(
830 format!("Item {} not found in module's exported items", self.name.name),
831 self.name.as_source_ranges(),
832 )));
833 }
834
835 exec_state
836 .stack()
837 .memory
838 .get_from(&self.name.name, env, self.name.as_source_range(), 0)
839 }
840}
841
842impl Node<MemberExpression> {
843 fn get_result(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
844 let property = Property::try_from(self.computed, self.property.clone(), exec_state, self.into())?;
845 let object = match &self.object {
846 MemberObject::MemberExpression(member_expr) => member_expr.get_result(exec_state)?,
848 MemberObject::Identifier(identifier) => {
849 let value = exec_state.stack().get(&identifier.name, identifier.into())?;
850 value.clone()
851 }
852 };
853
854 match (object, property, self.computed) {
856 (KclValue::Object { value: map, meta: _ }, Property::String(property), false) => {
857 if let Some(value) = map.get(&property) {
858 Ok(value.to_owned())
859 } else {
860 Err(KclError::UndefinedValue(KclErrorDetails::new(
861 format!("Property '{property}' not found in object"),
862 vec![self.clone().into()],
863 )))
864 }
865 }
866 (KclValue::Object { .. }, Property::String(property), true) => {
867 Err(KclError::Semantic(KclErrorDetails::new(
868 format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
869 vec![self.clone().into()],
870 )))
871 }
872 (KclValue::Object { .. }, p, _) => {
873 let t = p.type_name();
874 let article = article_for(t);
875 Err(KclError::Semantic(KclErrorDetails::new(
876 format!("Only strings can be used as the property of an object, but you're using {article} {t}",),
877 vec![self.clone().into()],
878 )))
879 }
880 (KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => {
881 let value_of_arr = arr.get(index);
882 if let Some(value) = value_of_arr {
883 Ok(value.to_owned())
884 } else {
885 Err(KclError::UndefinedValue(KclErrorDetails::new(
886 format!("The array doesn't have any item at index {index}"),
887 vec![self.clone().into()],
888 )))
889 }
890 }
891 (KclValue::HomArray { .. }, p, _) => {
892 let t = p.type_name();
893 let article = article_for(t);
894 Err(KclError::Semantic(KclErrorDetails::new(
895 format!("Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",),
896 vec![self.clone().into()],
897 )))
898 }
899 (KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => Ok(KclValue::Sketch {
900 value: Box::new(value.sketch),
901 }),
902 (KclValue::Sketch { value: sk }, Property::String(prop), false) if prop == "tags" => Ok(KclValue::Object {
903 meta: vec![Metadata {
904 source_range: SourceRange::from(self.clone()),
905 }],
906 value: sk
907 .tags
908 .iter()
909 .map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
910 .collect(),
911 }),
912 (being_indexed, _, _) => {
913 let t = being_indexed.human_friendly_type();
914 let article = article_for(&t);
915 Err(KclError::Semantic(KclErrorDetails::new(
916 format!("Only arrays can be indexed, but you're trying to index {article} {t}"),
917 vec![self.clone().into()],
918 )))
919 }
920 }
921 }
922}
923
924impl Node<BinaryExpression> {
925 #[async_recursion]
926 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
927 let left_value = self.left.get_result(exec_state, ctx).await?;
928 let right_value = self.right.get_result(exec_state, ctx).await?;
929 let mut meta = left_value.metadata();
930 meta.extend(right_value.metadata());
931
932 if self.operator == BinaryOperator::Add {
934 if let (KclValue::String { value: left, meta: _ }, KclValue::String { value: right, meta: _ }) =
935 (&left_value, &right_value)
936 {
937 return Ok(KclValue::String {
938 value: format!("{}{}", left, right),
939 meta,
940 });
941 }
942 }
943
944 if self.operator == BinaryOperator::Add || self.operator == BinaryOperator::Or {
946 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
947 let args = Args::new(Default::default(), self.into(), ctx.clone(), None);
948 let result = crate::std::csg::inner_union(
949 vec![*left.clone(), *right.clone()],
950 Default::default(),
951 exec_state,
952 args,
953 )
954 .await?;
955 return Ok(result.into());
956 }
957 } else if self.operator == BinaryOperator::Sub {
958 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
960 let args = Args::new(Default::default(), self.into(), ctx.clone(), None);
961 let result = crate::std::csg::inner_subtract(
962 vec![*left.clone()],
963 vec![*right.clone()],
964 Default::default(),
965 exec_state,
966 args,
967 )
968 .await?;
969 return Ok(result.into());
970 }
971 } else if self.operator == BinaryOperator::And {
972 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
974 let args = Args::new(Default::default(), self.into(), ctx.clone(), None);
975 let result = crate::std::csg::inner_intersect(
976 vec![*left.clone(), *right.clone()],
977 Default::default(),
978 exec_state,
979 args,
980 )
981 .await?;
982 return Ok(result.into());
983 }
984 }
985
986 if self.operator == BinaryOperator::Or || self.operator == BinaryOperator::And {
988 let KclValue::Bool {
989 value: left_value,
990 meta: _,
991 } = left_value
992 else {
993 return Err(KclError::Semantic(KclErrorDetails::new(
994 format!(
995 "Cannot apply logical operator to non-boolean value: {}",
996 left_value.human_friendly_type()
997 ),
998 vec![self.left.clone().into()],
999 )));
1000 };
1001 let KclValue::Bool {
1002 value: right_value,
1003 meta: _,
1004 } = right_value
1005 else {
1006 return Err(KclError::Semantic(KclErrorDetails::new(
1007 format!(
1008 "Cannot apply logical operator to non-boolean value: {}",
1009 right_value.human_friendly_type()
1010 ),
1011 vec![self.right.clone().into()],
1012 )));
1013 };
1014 let raw_value = match self.operator {
1015 BinaryOperator::Or => left_value || right_value,
1016 BinaryOperator::And => left_value && right_value,
1017 _ => unreachable!(),
1018 };
1019 return Ok(KclValue::Bool { value: raw_value, meta });
1020 }
1021
1022 let left = number_as_f64(&left_value, self.left.clone().into())?;
1023 let right = number_as_f64(&right_value, self.right.clone().into())?;
1024
1025 let value = match self.operator {
1026 BinaryOperator::Add => {
1027 let (l, r, ty) = NumericType::combine_eq_coerce(left, right);
1028 self.warn_on_unknown(&ty, "Adding", exec_state);
1029 KclValue::Number { value: l + r, meta, ty }
1030 }
1031 BinaryOperator::Sub => {
1032 let (l, r, ty) = NumericType::combine_eq_coerce(left, right);
1033 self.warn_on_unknown(&ty, "Subtracting", exec_state);
1034 KclValue::Number { value: l - r, meta, ty }
1035 }
1036 BinaryOperator::Mul => {
1037 let (l, r, ty) = NumericType::combine_mul(left, right);
1038 self.warn_on_unknown(&ty, "Multiplying", exec_state);
1039 KclValue::Number { value: l * r, meta, ty }
1040 }
1041 BinaryOperator::Div => {
1042 let (l, r, ty) = NumericType::combine_div(left, right);
1043 self.warn_on_unknown(&ty, "Dividing", exec_state);
1044 KclValue::Number { value: l / r, meta, ty }
1045 }
1046 BinaryOperator::Mod => {
1047 let (l, r, ty) = NumericType::combine_div(left, right);
1048 self.warn_on_unknown(&ty, "Modulo of", exec_state);
1049 KclValue::Number { value: l % r, meta, ty }
1050 }
1051 BinaryOperator::Pow => KclValue::Number {
1052 value: left.n.powf(right.n),
1053 meta,
1054 ty: exec_state.current_default_units(),
1055 },
1056 BinaryOperator::Neq => {
1057 let (l, r, ty) = NumericType::combine_eq(left, right);
1058 self.warn_on_unknown(&ty, "Comparing", exec_state);
1059 KclValue::Bool { value: l != r, meta }
1060 }
1061 BinaryOperator::Gt => {
1062 let (l, r, ty) = NumericType::combine_eq(left, right);
1063 self.warn_on_unknown(&ty, "Comparing", exec_state);
1064 KclValue::Bool { value: l > r, meta }
1065 }
1066 BinaryOperator::Gte => {
1067 let (l, r, ty) = NumericType::combine_eq(left, right);
1068 self.warn_on_unknown(&ty, "Comparing", exec_state);
1069 KclValue::Bool { value: l >= r, meta }
1070 }
1071 BinaryOperator::Lt => {
1072 let (l, r, ty) = NumericType::combine_eq(left, right);
1073 self.warn_on_unknown(&ty, "Comparing", exec_state);
1074 KclValue::Bool { value: l < r, meta }
1075 }
1076 BinaryOperator::Lte => {
1077 let (l, r, ty) = NumericType::combine_eq(left, right);
1078 self.warn_on_unknown(&ty, "Comparing", exec_state);
1079 KclValue::Bool { value: l <= r, meta }
1080 }
1081 BinaryOperator::Eq => {
1082 let (l, r, ty) = NumericType::combine_eq(left, right);
1083 self.warn_on_unknown(&ty, "Comparing", exec_state);
1084 KclValue::Bool { value: l == r, meta }
1085 }
1086 BinaryOperator::And | BinaryOperator::Or => unreachable!(),
1087 };
1088
1089 Ok(value)
1090 }
1091
1092 fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
1093 if ty == &NumericType::Unknown {
1094 exec_state.warn(CompilationError::err(
1096 self.as_source_range(),
1097 format!("{} numbers which have unknown or incompatible units.", verb),
1098 ));
1099 }
1100 }
1101}
1102
1103impl Node<UnaryExpression> {
1104 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1105 if self.operator == UnaryOperator::Not {
1106 let value = self.argument.get_result(exec_state, ctx).await?;
1107 let KclValue::Bool {
1108 value: bool_value,
1109 meta: _,
1110 } = value
1111 else {
1112 return Err(KclError::Semantic(KclErrorDetails::new(
1113 format!(
1114 "Cannot apply unary operator ! to non-boolean value: {}",
1115 value.human_friendly_type()
1116 ),
1117 vec![self.into()],
1118 )));
1119 };
1120 let meta = vec![Metadata {
1121 source_range: self.into(),
1122 }];
1123 let negated = KclValue::Bool {
1124 value: !bool_value,
1125 meta,
1126 };
1127
1128 return Ok(negated);
1129 }
1130
1131 let value = &self.argument.get_result(exec_state, ctx).await?;
1132 let err = || {
1133 KclError::Semantic(KclErrorDetails::new(
1134 format!(
1135 "You can only negate numbers, planes, or lines, but this is a {}",
1136 value.human_friendly_type()
1137 ),
1138 vec![self.into()],
1139 ))
1140 };
1141 match value {
1142 KclValue::Number { value, ty, .. } => {
1143 let meta = vec![Metadata {
1144 source_range: self.into(),
1145 }];
1146 Ok(KclValue::Number {
1147 value: -value,
1148 meta,
1149 ty: ty.clone(),
1150 })
1151 }
1152 KclValue::Plane { value } => {
1153 let mut plane = value.clone();
1154 if plane.info.x_axis.x != 0.0 {
1155 plane.info.x_axis.x *= -1.0;
1156 }
1157 if plane.info.x_axis.y != 0.0 {
1158 plane.info.x_axis.y *= -1.0;
1159 }
1160 if plane.info.x_axis.z != 0.0 {
1161 plane.info.x_axis.z *= -1.0;
1162 }
1163
1164 plane.value = PlaneType::Uninit;
1165 plane.id = exec_state.next_uuid();
1166 Ok(KclValue::Plane { value: plane })
1167 }
1168 KclValue::Object { value: values, meta } => {
1169 let Some(direction) = values.get("direction") else {
1171 return Err(err());
1172 };
1173
1174 let direction = match direction {
1175 KclValue::Tuple { value: values, meta } => {
1176 let values = values
1177 .iter()
1178 .map(|v| match v {
1179 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
1180 value: *value * -1.0,
1181 ty: ty.clone(),
1182 meta: meta.clone(),
1183 }),
1184 _ => Err(err()),
1185 })
1186 .collect::<Result<Vec<_>, _>>()?;
1187
1188 KclValue::Tuple {
1189 value: values,
1190 meta: meta.clone(),
1191 }
1192 }
1193 KclValue::HomArray {
1194 value: values,
1195 ty: ty @ RuntimeType::Primitive(PrimitiveType::Number(_)),
1196 } => {
1197 let values = values
1198 .iter()
1199 .map(|v| match v {
1200 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
1201 value: *value * -1.0,
1202 ty: ty.clone(),
1203 meta: meta.clone(),
1204 }),
1205 _ => Err(err()),
1206 })
1207 .collect::<Result<Vec<_>, _>>()?;
1208
1209 KclValue::HomArray {
1210 value: values,
1211 ty: ty.clone(),
1212 }
1213 }
1214 _ => return Err(err()),
1215 };
1216
1217 let mut value = values.clone();
1218 value.insert("direction".to_owned(), direction);
1219 Ok(KclValue::Object {
1220 value,
1221 meta: meta.clone(),
1222 })
1223 }
1224 _ => Err(err()),
1225 }
1226 }
1227}
1228
1229pub(crate) async fn execute_pipe_body(
1230 exec_state: &mut ExecState,
1231 body: &[Expr],
1232 source_range: SourceRange,
1233 ctx: &ExecutorContext,
1234) -> Result<KclValue, KclError> {
1235 let Some((first, body)) = body.split_first() else {
1236 return Err(KclError::Semantic(KclErrorDetails::new(
1237 "Pipe expressions cannot be empty".to_owned(),
1238 vec![source_range],
1239 )));
1240 };
1241 let meta = Metadata {
1246 source_range: SourceRange::from(first),
1247 };
1248 let output = ctx
1249 .execute_expr(first, exec_state, &meta, &[], StatementKind::Expression)
1250 .await?;
1251
1252 let previous_pipe_value = std::mem::replace(&mut exec_state.mod_local.pipe_value, Some(output));
1256 let result = inner_execute_pipe_body(exec_state, body, ctx).await;
1258 exec_state.mod_local.pipe_value = previous_pipe_value;
1260
1261 result
1262}
1263
1264#[async_recursion]
1267async fn inner_execute_pipe_body(
1268 exec_state: &mut ExecState,
1269 body: &[Expr],
1270 ctx: &ExecutorContext,
1271) -> Result<KclValue, KclError> {
1272 for expression in body {
1273 if let Expr::TagDeclarator(_) = expression {
1274 return Err(KclError::Semantic(KclErrorDetails::new(
1275 format!("This cannot be in a PipeExpression: {:?}", expression),
1276 vec![expression.into()],
1277 )));
1278 }
1279 let metadata = Metadata {
1280 source_range: SourceRange::from(expression),
1281 };
1282 let output = ctx
1283 .execute_expr(expression, exec_state, &metadata, &[], StatementKind::Expression)
1284 .await?;
1285 exec_state.mod_local.pipe_value = Some(output);
1286 }
1287 let final_output = exec_state.mod_local.pipe_value.take().unwrap();
1289 Ok(final_output)
1290}
1291
1292impl Node<TagDeclarator> {
1293 pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
1294 let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
1295 value: self.name.clone(),
1296 info: Vec::new(),
1297 meta: vec![Metadata {
1298 source_range: self.into(),
1299 }],
1300 }));
1301
1302 exec_state
1303 .mut_stack()
1304 .add(self.name.clone(), memory_item.clone(), self.into())?;
1305
1306 Ok(self.into())
1307 }
1308}
1309
1310impl Node<ArrayExpression> {
1311 #[async_recursion]
1312 pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1313 let mut results = Vec::with_capacity(self.elements.len());
1314
1315 for element in &self.elements {
1316 let metadata = Metadata::from(element);
1317 let value = ctx
1320 .execute_expr(element, exec_state, &metadata, &[], StatementKind::Expression)
1321 .await?;
1322
1323 results.push(value);
1324 }
1325
1326 Ok(KclValue::HomArray {
1327 value: results,
1328 ty: RuntimeType::Primitive(PrimitiveType::Any),
1329 })
1330 }
1331}
1332
1333impl Node<ArrayRangeExpression> {
1334 #[async_recursion]
1335 pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1336 let metadata = Metadata::from(&self.start_element);
1337 let start_val = ctx
1338 .execute_expr(
1339 &self.start_element,
1340 exec_state,
1341 &metadata,
1342 &[],
1343 StatementKind::Expression,
1344 )
1345 .await?;
1346 let (start, start_ty) = start_val
1347 .as_int_with_ty()
1348 .ok_or(KclError::Semantic(KclErrorDetails::new(
1349 format!("Expected int but found {}", start_val.human_friendly_type()),
1350 vec![self.into()],
1351 )))?;
1352 let metadata = Metadata::from(&self.end_element);
1353 let end_val = ctx
1354 .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
1355 .await?;
1356 let (end, end_ty) = end_val.as_int_with_ty().ok_or(KclError::Semantic(KclErrorDetails::new(
1357 format!("Expected int but found {}", end_val.human_friendly_type()),
1358 vec![self.into()],
1359 )))?;
1360
1361 if start_ty != end_ty {
1362 let start = start_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: start_ty });
1363 let start = fmt::human_display_number(start.n, start.ty);
1364 let end = end_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: end_ty });
1365 let end = fmt::human_display_number(end.n, end.ty);
1366 return Err(KclError::Semantic(KclErrorDetails::new(
1367 format!("Range start and end must be of the same type, but found {start} and {end}"),
1368 vec![self.into()],
1369 )));
1370 }
1371
1372 if end < start {
1373 return Err(KclError::Semantic(KclErrorDetails::new(
1374 format!("Range start is greater than range end: {start} .. {end}"),
1375 vec![self.into()],
1376 )));
1377 }
1378
1379 let range: Vec<_> = if self.end_inclusive {
1380 (start..=end).collect()
1381 } else {
1382 (start..end).collect()
1383 };
1384
1385 let meta = vec![Metadata {
1386 source_range: self.into(),
1387 }];
1388
1389 Ok(KclValue::HomArray {
1390 value: range
1391 .into_iter()
1392 .map(|num| KclValue::Number {
1393 value: num as f64,
1394 ty: start_ty.clone(),
1395 meta: meta.clone(),
1396 })
1397 .collect(),
1398 ty: RuntimeType::Primitive(PrimitiveType::Number(start_ty)),
1399 })
1400 }
1401}
1402
1403impl Node<ObjectExpression> {
1404 #[async_recursion]
1405 pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1406 let mut object = HashMap::with_capacity(self.properties.len());
1407 for property in &self.properties {
1408 let metadata = Metadata::from(&property.value);
1409 let result = ctx
1410 .execute_expr(&property.value, exec_state, &metadata, &[], StatementKind::Expression)
1411 .await?;
1412
1413 object.insert(property.key.name.clone(), result);
1414 }
1415
1416 Ok(KclValue::Object {
1417 value: object,
1418 meta: vec![Metadata {
1419 source_range: self.into(),
1420 }],
1421 })
1422 }
1423}
1424
1425fn article_for<S: AsRef<str>>(s: S) -> &'static str {
1426 if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
1428 "an"
1429 } else {
1430 "a"
1431 }
1432}
1433
1434fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
1435 v.as_ty_f64().ok_or_else(|| {
1436 let actual_type = v.human_friendly_type();
1437 KclError::Semantic(KclErrorDetails::new(
1438 format!("Expected a number, but found {actual_type}",),
1439 vec![source_range],
1440 ))
1441 })
1442}
1443
1444impl Node<IfExpression> {
1445 #[async_recursion]
1446 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1447 let cond = ctx
1449 .execute_expr(
1450 &self.cond,
1451 exec_state,
1452 &Metadata::from(self),
1453 &[],
1454 StatementKind::Expression,
1455 )
1456 .await?
1457 .get_bool()?;
1458 if cond {
1459 let block_result = ctx.exec_block(&self.then_val, exec_state, BodyType::Block).await?;
1460 return Ok(block_result.unwrap());
1464 }
1465
1466 for else_if in &self.else_ifs {
1468 let cond = ctx
1469 .execute_expr(
1470 &else_if.cond,
1471 exec_state,
1472 &Metadata::from(self),
1473 &[],
1474 StatementKind::Expression,
1475 )
1476 .await?
1477 .get_bool()?;
1478 if cond {
1479 let block_result = ctx.exec_block(&else_if.then_val, exec_state, BodyType::Block).await?;
1480 return Ok(block_result.unwrap());
1484 }
1485 }
1486
1487 ctx.exec_block(&self.final_else, exec_state, BodyType::Block)
1489 .await
1490 .map(|expr| expr.unwrap())
1491 }
1492}
1493
1494#[derive(Debug)]
1495enum Property {
1496 UInt(usize),
1497 String(String),
1498}
1499
1500impl Property {
1501 fn try_from(
1502 computed: bool,
1503 value: LiteralIdentifier,
1504 exec_state: &ExecState,
1505 sr: SourceRange,
1506 ) -> Result<Self, KclError> {
1507 let property_sr = vec![sr];
1508 let property_src: SourceRange = value.clone().into();
1509 match value {
1510 LiteralIdentifier::Identifier(identifier) => {
1511 let name = &identifier.name;
1512 if !computed {
1513 Ok(Property::String(name.to_string()))
1515 } else {
1516 let prop = exec_state.stack().get(name, property_src)?;
1519 jvalue_to_prop(prop, property_sr, name)
1520 }
1521 }
1522 LiteralIdentifier::Literal(literal) => {
1523 let value = literal.value.clone();
1524 match value {
1525 LiteralValue::Number { value, .. } => {
1526 if let Some(x) = crate::try_f64_to_usize(value) {
1527 Ok(Property::UInt(x))
1528 } else {
1529 Err(KclError::Semantic(KclErrorDetails::new(
1530 format!("{value} is not a valid index, indices must be whole numbers >= 0"),
1531 property_sr,
1532 )))
1533 }
1534 }
1535 _ => Err(KclError::Semantic(KclErrorDetails::new(
1536 "Only numbers (>= 0) can be indexes".to_owned(),
1537 vec![sr],
1538 ))),
1539 }
1540 }
1541 }
1542 }
1543}
1544
1545fn jvalue_to_prop(value: &KclValue, property_sr: Vec<SourceRange>, name: &str) -> Result<Property, KclError> {
1546 let make_err = |message: String| Err::<Property, _>(KclError::Semantic(KclErrorDetails::new(message, property_sr)));
1547 match value {
1548 KclValue::Number{value: num, .. } => {
1549 let num = *num;
1550 if num < 0.0 {
1551 return make_err(format!("'{num}' is negative, so you can't index an array with it"))
1552 }
1553 let nearest_int = crate::try_f64_to_usize(num);
1554 if let Some(nearest_int) = nearest_int {
1555 Ok(Property::UInt(nearest_int))
1556 } else {
1557 make_err(format!("'{num}' is not an integer, so you can't index an array with it"))
1558 }
1559 }
1560 KclValue::String{value: x, meta:_} => Ok(Property::String(x.to_owned())),
1561 _ => {
1562 make_err(format!("{name} is not a valid property/index, you can only use a string to get the property of an object, or an int (>= 0) to get an item in an array"))
1563 }
1564 }
1565}
1566
1567impl Property {
1568 fn type_name(&self) -> &'static str {
1569 match self {
1570 Property::UInt(_) => "number",
1571 Property::String(_) => "string",
1572 }
1573 }
1574}
1575
1576impl Node<PipeExpression> {
1577 #[async_recursion]
1578 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1579 execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
1580 }
1581}
1582
1583#[cfg(test)]
1584mod test {
1585 use std::sync::Arc;
1586
1587 use tokio::io::AsyncWriteExt;
1588
1589 use super::*;
1590 use crate::{
1591 exec::UnitType,
1592 execution::{parse_execute, ContextType},
1593 ExecutorSettings, UnitLen,
1594 };
1595
1596 #[tokio::test(flavor = "multi_thread")]
1597 async fn ascription() {
1598 let program = r#"
1599a = 42: number
1600b = a: number
1601p = {
1602 origin = { x = 0, y = 0, z = 0 },
1603 xAxis = { x = 1, y = 0, z = 0 },
1604 yAxis = { x = 0, y = 1, z = 0 },
1605 zAxis = { x = 0, y = 0, z = 1 }
1606}: Plane
1607arr1 = [42]: [number(cm)]
1608"#;
1609
1610 let result = parse_execute(program).await.unwrap();
1611 let mem = result.exec_state.stack();
1612 assert!(matches!(
1613 mem.memory
1614 .get_from("p", result.mem_env, SourceRange::default(), 0)
1615 .unwrap(),
1616 KclValue::Plane { .. }
1617 ));
1618 let arr1 = mem
1619 .memory
1620 .get_from("arr1", result.mem_env, SourceRange::default(), 0)
1621 .unwrap();
1622 if let KclValue::HomArray { value, ty } = arr1 {
1623 assert_eq!(value.len(), 1, "Expected Vec with specific length: found {:?}", value);
1624 assert_eq!(*ty, RuntimeType::known_length(UnitLen::Cm));
1625 if let KclValue::Number { value, ty, .. } = &value[0] {
1627 assert_eq!(*value, 4.2);
1629 assert_eq!(*ty, NumericType::Known(UnitType::Length(UnitLen::Cm)));
1630 } else {
1631 panic!("Expected a number; found {:?}", value[0]);
1632 }
1633 } else {
1634 panic!("Expected HomArray; found {arr1:?}");
1635 }
1636
1637 let program = r#"
1638a = 42: string
1639"#;
1640 let result = parse_execute(program).await;
1641 let err = result.unwrap_err();
1642 assert!(
1643 err.to_string()
1644 .contains("could not coerce number(default units) value to type string"),
1645 "Expected error but found {err:?}"
1646 );
1647
1648 let program = r#"
1649a = 42: Plane
1650"#;
1651 let result = parse_execute(program).await;
1652 let err = result.unwrap_err();
1653 assert!(
1654 err.to_string()
1655 .contains("could not coerce number(default units) value to type Plane"),
1656 "Expected error but found {err:?}"
1657 );
1658
1659 let program = r#"
1660arr = [0]: [string]
1661"#;
1662 let result = parse_execute(program).await;
1663 let err = result.unwrap_err();
1664 assert!(
1665 err.to_string()
1666 .contains("could not coerce [any; 1] value to type [string]"),
1667 "Expected error but found {err:?}"
1668 );
1669
1670 let program = r#"
1671mixedArr = [0, "a"]: [number(mm)]
1672"#;
1673 let result = parse_execute(program).await;
1674 let err = result.unwrap_err();
1675 assert!(
1676 err.to_string()
1677 .contains("could not coerce [any; 2] value to type [number(mm)]"),
1678 "Expected error but found {err:?}"
1679 );
1680 }
1681
1682 #[tokio::test(flavor = "multi_thread")]
1683 async fn neg_plane() {
1684 let program = r#"
1685p = {
1686 origin = { x = 0, y = 0, z = 0 },
1687 xAxis = { x = 1, y = 0, z = 0 },
1688 yAxis = { x = 0, y = 1, z = 0 },
1689}: Plane
1690p2 = -p
1691"#;
1692
1693 let result = parse_execute(program).await.unwrap();
1694 let mem = result.exec_state.stack();
1695 match mem
1696 .memory
1697 .get_from("p2", result.mem_env, SourceRange::default(), 0)
1698 .unwrap()
1699 {
1700 KclValue::Plane { value } => {
1701 assert_eq!(value.info.x_axis.x, -1.0);
1702 assert_eq!(value.info.x_axis.y, 0.0);
1703 assert_eq!(value.info.x_axis.z, 0.0);
1704 }
1705 _ => unreachable!(),
1706 }
1707 }
1708
1709 #[tokio::test(flavor = "multi_thread")]
1710 async fn multiple_returns() {
1711 let program = r#"fn foo() {
1712 return 0
1713 return 42
1714}
1715
1716a = foo()
1717"#;
1718
1719 let result = parse_execute(program).await;
1720 assert!(result.unwrap_err().to_string().contains("return"));
1721 }
1722
1723 #[tokio::test(flavor = "multi_thread")]
1724 async fn load_all_modules() {
1725 let program_a_kcl = r#"
1727export a = 1
1728"#;
1729 let program_b_kcl = r#"
1731import a from 'a.kcl'
1732
1733export b = a + 1
1734"#;
1735 let program_c_kcl = r#"
1737import a from 'a.kcl'
1738
1739export c = a + 2
1740"#;
1741
1742 let main_kcl = r#"
1744import b from 'b.kcl'
1745import c from 'c.kcl'
1746
1747d = b + c
1748"#;
1749
1750 let main = crate::parsing::parse_str(main_kcl, ModuleId::default())
1751 .parse_errs_as_err()
1752 .unwrap();
1753
1754 let tmpdir = tempfile::TempDir::with_prefix("zma_kcl_load_all_modules").unwrap();
1755
1756 tokio::fs::File::create(tmpdir.path().join("main.kcl"))
1757 .await
1758 .unwrap()
1759 .write_all(main_kcl.as_bytes())
1760 .await
1761 .unwrap();
1762
1763 tokio::fs::File::create(tmpdir.path().join("a.kcl"))
1764 .await
1765 .unwrap()
1766 .write_all(program_a_kcl.as_bytes())
1767 .await
1768 .unwrap();
1769
1770 tokio::fs::File::create(tmpdir.path().join("b.kcl"))
1771 .await
1772 .unwrap()
1773 .write_all(program_b_kcl.as_bytes())
1774 .await
1775 .unwrap();
1776
1777 tokio::fs::File::create(tmpdir.path().join("c.kcl"))
1778 .await
1779 .unwrap()
1780 .write_all(program_c_kcl.as_bytes())
1781 .await
1782 .unwrap();
1783
1784 let exec_ctxt = ExecutorContext {
1785 engine: Arc::new(Box::new(
1786 crate::engine::conn_mock::EngineConnection::new()
1787 .await
1788 .map_err(|err| {
1789 KclError::Internal(KclErrorDetails::new(
1790 format!("Failed to create mock engine connection: {}", err),
1791 vec![SourceRange::default()],
1792 ))
1793 })
1794 .unwrap(),
1795 )),
1796 fs: Arc::new(crate::fs::FileManager::new()),
1797 settings: ExecutorSettings {
1798 project_directory: Some(crate::TypedPath(tmpdir.path().into())),
1799 ..Default::default()
1800 },
1801 stdlib: Arc::new(crate::std::StdLib::new()),
1802 context_type: ContextType::Mock,
1803 };
1804 let mut exec_state = ExecState::new(&exec_ctxt);
1805
1806 exec_ctxt
1807 .run(
1808 &crate::Program {
1809 ast: main.clone(),
1810 original_file_contents: "".to_owned(),
1811 },
1812 &mut exec_state,
1813 )
1814 .await
1815 .unwrap();
1816 }
1817
1818 #[tokio::test(flavor = "multi_thread")]
1819 async fn user_coercion() {
1820 let program = r#"fn foo(x: Axis2d) {
1821 return 0
1822}
1823
1824foo(x = { direction = [0, 0], origin = [0, 0]})
1825"#;
1826
1827 parse_execute(program).await.unwrap();
1828
1829 let program = r#"fn foo(x: Axis3d) {
1830 return 0
1831}
1832
1833foo(x = { direction = [0, 0], origin = [0, 0]})
1834"#;
1835
1836 parse_execute(program).await.unwrap_err();
1837 }
1838
1839 #[tokio::test(flavor = "multi_thread")]
1840 async fn coerce_return() {
1841 let program = r#"fn foo(): number(mm) {
1842 return 42
1843}
1844
1845a = foo()
1846"#;
1847
1848 parse_execute(program).await.unwrap();
1849
1850 let program = r#"fn foo(): number(mm) {
1851 return { bar: 42 }
1852}
1853
1854a = foo()
1855"#;
1856
1857 parse_execute(program).await.unwrap_err();
1858 }
1859
1860 #[tokio::test(flavor = "multi_thread")]
1861 async fn test_sensible_error_when_missing_equals_in_kwarg() {
1862 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)"]
1863 .into_iter()
1864 .enumerate()
1865 {
1866 let program = format!(
1867 "fn foo() {{ return 0 }}
1868z = 0
1869fn f(x, y, z) {{ return 0 }}
1870{call}"
1871 );
1872 let err = parse_execute(&program).await.unwrap_err();
1873 let msg = err.message();
1874 assert!(
1875 msg.contains("This argument needs a label, but it doesn't have one"),
1876 "failed test {i}: {msg}"
1877 );
1878 assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
1879 if i == 0 {
1880 assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
1881 }
1882 }
1883 }
1884
1885 #[tokio::test(flavor = "multi_thread")]
1886 async fn default_param_for_unlabeled() {
1887 let ast = r#"fn myExtrude(@sk, length) {
1890 return extrude(sk, length)
1891}
1892sketch001 = startSketchOn(XY)
1893 |> circle(center = [0, 0], radius = 93.75)
1894 |> myExtrude(length = 40)
1895"#;
1896
1897 parse_execute(ast).await.unwrap();
1898 }
1899
1900 #[tokio::test(flavor = "multi_thread")]
1901 async fn dont_use_unlabelled_as_input() {
1902 let ast = r#"length = 10
1904startSketchOn(XY)
1905 |> circle(center = [0, 0], radius = 93.75)
1906 |> extrude(length)
1907"#;
1908
1909 parse_execute(ast).await.unwrap();
1910 }
1911
1912 #[tokio::test(flavor = "multi_thread")]
1913 async fn ascription_in_binop() {
1914 let ast = r#"foo = tan(0): number(rad) - 4deg"#;
1915 parse_execute(ast).await.unwrap();
1916 }
1917
1918 #[tokio::test(flavor = "multi_thread")]
1919 async fn neg_sqrt() {
1920 let ast = r#"bad = sqrt(-2)"#;
1921
1922 let e = parse_execute(ast).await.unwrap_err();
1923 assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
1925 }
1926
1927 #[tokio::test(flavor = "multi_thread")]
1928 async fn coerce_unknown_to_length() {
1929 let ast = r#"x = 2mm * 2mm
1930y = x: number(Length)"#;
1931 let e = parse_execute(ast).await.unwrap_err();
1932 assert!(
1933 e.message().contains("could not coerce"),
1934 "Error message: '{}'",
1935 e.message()
1936 );
1937
1938 let ast = r#"x = 2mm
1939y = x: number(Length)"#;
1940 let result = parse_execute(ast).await.unwrap();
1941 let mem = result.exec_state.stack();
1942 let num = mem
1943 .memory
1944 .get_from("y", result.mem_env, SourceRange::default(), 0)
1945 .unwrap()
1946 .as_ty_f64()
1947 .unwrap();
1948 assert_eq!(num.n, 2.0);
1949 assert_eq!(num.ty, NumericType::mm());
1950 }
1951}