kcl_lib/execution/
exec_ast.rs

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    /// Returns true if importing the prelude should be skipped.
41    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    /// Execute an AST's program.
121    #[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        // Iterate over the body of the program.
130        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                                // Extract the item from the module.
152                                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                                // Check that the item is allowed to be imported (in at least one namespace).
167                                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                                // Add the item to the current module.
186                                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                                    // Add the item to the current module.
204                                    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                    // Track exports.
283                    if let ItemVisibility::Export = variable_declaration.visibility {
284                        exec_state.mod_local.module_exports.push(var_name);
285                    }
286                    // Variable declaration can be the return value of a module.
287                    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                        // Do nothing for primitive types, they get special treatment and their declarations are just for documentation.
324                        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            // Flush the batch queue.
399            self.engine
400                .flush_batch(
401                    // True here tells the engine to flush all the end commands as well like fillets
402                    // and chamfers where the engine would otherwise eat the ID of the segments.
403                    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                // Add file path string to global state even if it fails to import
431                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                // TODO handle parsing errors properly
435                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                // Add file path string to global state even if it fails to import
448                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                // Add file path string to global state even if it fails to import
461                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        // DON'T EARLY RETURN! We need to restore the module repr
482
483        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        // DON'T EARLY RETURN! We need to restore the module repr
513
514        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                // It was an import cycle.  Keep the original message.
569                err.override_source_ranges(vec![source_range])
570            } else {
571                // TODO would be great to have line/column for the underlying error here
572                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                    // Snapshotting memory here is crucial for semantics so that we close
646                    // over variables. Variables defined lexically later shouldn't
647                    // be available to the function body.
648                    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                // TODO this lets us use the label as a variable name, but not as a tag in most cases
695                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 the number has unknown units but the user is explicitly specifying them, treat the value as having had it's units erased,
728    // rather than forcing the user to explicitly erase them.
729    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            // TODO: Don't use recursion here, use a loop.
847            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        // Check the property and object match -- e.g. ints for arrays, strs for objects.
855        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        // First check if we are doing string concatenation.
933        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        // Then check if we have solids.
945        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            // Check if we have solids.
959            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            // Check if we have solids.
973            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        // Check if we are doing logical operations on booleans.
987        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            // TODO suggest how to fix this
1095            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                // Special-case for negating line-like objects.
1170                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    // Evaluate the first element in the pipeline.
1242    // They use the pipe_value from some AST node above this, so that if pipe expression is nested in a larger pipe expression,
1243    // they use the % from the parent. After all, this pipe expression hasn't been executed yet, so it doesn't have any % value
1244    // of its own.
1245    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    // Now that we've evaluated the first child expression in the pipeline, following child expressions
1253    // should use the previous child expression for %.
1254    // This means there's no more need for the previous pipe_value from the parent AST node above this one.
1255    let previous_pipe_value = std::mem::replace(&mut exec_state.mod_local.pipe_value, Some(output));
1256    // Evaluate remaining elements.
1257    let result = inner_execute_pipe_body(exec_state, body, ctx).await;
1258    // Restore the previous pipe value.
1259    exec_state.mod_local.pipe_value = previous_pipe_value;
1260
1261    result
1262}
1263
1264/// Execute the tail of a pipe expression.  exec_state.pipe_value must be set by
1265/// the caller.
1266#[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    // Safe to unwrap here, because pipe_value always has something pushed in when the `match first` executes.
1288    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            // TODO: Carry statement kind here so that we know if we're
1318            // inside a variable declaration.
1319            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    // '[' is included since it's an array.
1427    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        // Check the `if` branch.
1448        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            // Block must end in an expression, so this has to be Some.
1461            // Enforced by the parser.
1462            // See https://github.com/KittyCAD/modeling-app/issues/4015
1463            return Ok(block_result.unwrap());
1464        }
1465
1466        // Check any `else if` branches.
1467        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                // Block must end in an expression, so this has to be Some.
1481                // Enforced by the parser.
1482                // See https://github.com/KittyCAD/modeling-app/issues/4015
1483                return Ok(block_result.unwrap());
1484            }
1485        }
1486
1487        // Run the final `else` branch.
1488        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                    // This is dot syntax. Treat the property as a literal.
1514                    Ok(Property::String(name.to_string()))
1515                } else {
1516                    // This is bracket syntax. Actually evaluate memory to
1517                    // compute the property.
1518                    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            // Compare, ignoring meta.
1626            if let KclValue::Number { value, ty, .. } = &value[0] {
1627                // Converted from mm to cm.
1628                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        // program a.kcl
1726        let program_a_kcl = r#"
1727export a = 1
1728"#;
1729        // program b.kcl
1730        let program_b_kcl = r#"
1731import a from 'a.kcl'
1732
1733export b = a + 1
1734"#;
1735        // program c.kcl
1736        let program_c_kcl = r#"
1737import a from 'a.kcl'
1738
1739export c = a + 2
1740"#;
1741
1742        // program main.kcl
1743        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        // Tests that the input param for myExtrude is taken from the pipeline value and same-name
1888        // keyword args.
1889        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        // `length` should be used as the `length` argument to extrude, not the unlabelled input
1903        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        // Make sure we get a useful error message and not an engine error.
1924        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}