1#[cfg(feature = "artifact-graph")]
4use std::collections::BTreeMap;
5use std::sync::Arc;
6
7use anyhow::Result;
8#[cfg(feature = "artifact-graph")]
9pub use artifact::Artifact;
10#[cfg(feature = "artifact-graph")]
11pub use artifact::ArtifactCommand;
12#[cfg(feature = "artifact-graph")]
13pub use artifact::ArtifactGraph;
14#[cfg(feature = "artifact-graph")]
15pub use artifact::CapSubType;
16#[cfg(feature = "artifact-graph")]
17pub use artifact::CodeRef;
18#[cfg(feature = "artifact-graph")]
19pub use artifact::SketchBlock;
20#[cfg(feature = "artifact-graph")]
21pub use artifact::SketchBlockConstraint;
22#[cfg(feature = "artifact-graph")]
23pub use artifact::SketchBlockConstraintType;
24#[cfg(feature = "artifact-graph")]
25pub use artifact::StartSketchOnFace;
26#[cfg(feature = "artifact-graph")]
27pub use artifact::StartSketchOnPlane;
28use cache::GlobalState;
29pub use cache::bust_cache;
30pub use cache::clear_mem_cache;
31#[cfg(feature = "artifact-graph")]
32pub use cad_op::Group;
33pub use cad_op::Operation;
34pub use geometry::*;
35pub use id_generator::IdGenerator;
36pub(crate) use import::PreImportedGeometry;
37use indexmap::IndexMap;
38pub use kcl_value::KclObjectFields;
39pub use kcl_value::KclValue;
40use kcmc::ImageFormat;
41use kcmc::ModelingCmd;
42use kcmc::each_cmd as mcmd;
43use kcmc::ok_response::OkModelingCmdResponse;
44use kcmc::ok_response::output::TakeSnapshot;
45use kcmc::websocket::ModelingSessionData;
46use kcmc::websocket::OkWebSocketResponseData;
47use kittycad_modeling_cmds::id::ModelingCmdId;
48use kittycad_modeling_cmds::{self as kcmc};
49pub use memory::EnvironmentRef;
50pub(crate) use modeling::ModelingCmdMeta;
51use serde::Deserialize;
52use serde::Serialize;
53pub(crate) use sketch_solve::normalize_to_solver_distance_unit;
54pub(crate) use sketch_solve::solver_numeric_type;
55pub use sketch_transpiler::pre_execute_transpile;
56pub use sketch_transpiler::transpile_all_old_sketches_to_new;
57pub use sketch_transpiler::transpile_old_sketch_to_new;
58pub use sketch_transpiler::transpile_old_sketch_to_new_ast;
59pub use sketch_transpiler::transpile_old_sketch_to_new_with_execution;
60pub use state::ExecState;
61pub use state::MetaSettings;
62pub(crate) use state::ModuleArtifactState;
63use uuid::Uuid;
64
65use crate::CompilationError;
66use crate::ExecError;
67use crate::KclErrorWithOutputs;
68use crate::SourceRange;
69#[cfg(feature = "artifact-graph")]
70use crate::collections::AhashIndexSet;
71use crate::engine::EngineManager;
72use crate::engine::GridScaleBehavior;
73use crate::errors::KclError;
74use crate::errors::KclErrorDetails;
75use crate::execution::cache::CacheInformation;
76use crate::execution::cache::CacheResult;
77use crate::execution::import_graph::Universe;
78use crate::execution::import_graph::UniverseMap;
79use crate::execution::typed_path::TypedPath;
80#[cfg(feature = "artifact-graph")]
81use crate::front::Number;
82use crate::front::Object;
83use crate::front::ObjectId;
84use crate::fs::FileManager;
85use crate::modules::ModuleExecutionOutcome;
86use crate::modules::ModuleId;
87use crate::modules::ModulePath;
88use crate::modules::ModuleRepr;
89use crate::parsing::ast::types::Expr;
90use crate::parsing::ast::types::ImportPath;
91use crate::parsing::ast::types::NodeRef;
92
93pub(crate) mod annotations;
94#[cfg(feature = "artifact-graph")]
95mod artifact;
96pub(crate) mod cache;
97mod cad_op;
98mod exec_ast;
99pub mod fn_call;
100#[cfg(test)]
101#[cfg(feature = "artifact-graph")]
102mod freedom_analysis_tests;
103mod geometry;
104mod id_generator;
105mod import;
106mod import_graph;
107pub(crate) mod kcl_value;
108mod memory;
109mod modeling;
110mod sketch_solve;
111mod sketch_transpiler;
112mod state;
113pub mod typed_path;
114pub(crate) mod types;
115
116pub(crate) const SKETCH_BLOCK_PARAM_ON: &str = "on";
117pub(crate) const SKETCH_OBJECT_META: &str = "meta";
118pub(crate) const SKETCH_OBJECT_META_SKETCH: &str = "sketch";
119
120macro_rules! control_continue {
125 ($control_flow:expr) => {{
126 let cf = $control_flow;
127 if cf.is_some_return() {
128 return Ok(cf);
129 } else {
130 cf.into_value()
131 }
132 }};
133}
134pub(crate) use control_continue;
136
137macro_rules! early_return {
142 ($control_flow:expr) => {{
143 let cf = $control_flow;
144 if cf.is_some_return() {
145 return Err(EarlyReturn::from(cf));
146 } else {
147 cf.into_value()
148 }
149 }};
150}
151pub(crate) use early_return;
153
154#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize)]
155pub enum ControlFlowKind {
156 #[default]
157 Continue,
158 Exit,
159}
160
161impl ControlFlowKind {
162 pub fn is_some_return(&self) -> bool {
164 match self {
165 ControlFlowKind::Continue => false,
166 ControlFlowKind::Exit => true,
167 }
168 }
169}
170
171#[must_use = "You should always handle the control flow value when it is returned"]
172#[derive(Debug, Clone, PartialEq, Serialize)]
173pub struct KclValueControlFlow {
174 value: KclValue,
176 pub control: ControlFlowKind,
177}
178
179impl KclValue {
180 pub(crate) fn continue_(self) -> KclValueControlFlow {
181 KclValueControlFlow {
182 value: self,
183 control: ControlFlowKind::Continue,
184 }
185 }
186
187 pub(crate) fn exit(self) -> KclValueControlFlow {
188 KclValueControlFlow {
189 value: self,
190 control: ControlFlowKind::Exit,
191 }
192 }
193}
194
195impl KclValueControlFlow {
196 pub fn is_some_return(&self) -> bool {
198 self.control.is_some_return()
199 }
200
201 pub(crate) fn into_value(self) -> KclValue {
202 self.value
203 }
204}
205
206#[must_use = "You should always handle the control flow value when it is returned"]
213#[derive(Debug, Clone)]
214pub(crate) enum EarlyReturn {
215 Value(KclValueControlFlow),
217 Error(KclError),
219}
220
221impl From<KclValueControlFlow> for EarlyReturn {
222 fn from(cf: KclValueControlFlow) -> Self {
223 EarlyReturn::Value(cf)
224 }
225}
226
227impl From<KclError> for EarlyReturn {
228 fn from(err: KclError) -> Self {
229 EarlyReturn::Error(err)
230 }
231}
232
233pub(crate) enum StatementKind<'a> {
234 Declaration { name: &'a str },
235 Expression,
236}
237
238#[derive(Debug, Clone, Copy)]
239pub enum PreserveMem {
240 Normal,
241 Always,
242}
243
244impl PreserveMem {
245 fn normal(self) -> bool {
246 match self {
247 PreserveMem::Normal => true,
248 PreserveMem::Always => false,
249 }
250 }
251}
252
253#[derive(Debug, Clone, Serialize, ts_rs::TS, PartialEq)]
255#[ts(export)]
256#[serde(rename_all = "camelCase")]
257pub struct ExecOutcome {
258 pub variables: IndexMap<String, KclValue>,
260 #[cfg(feature = "artifact-graph")]
263 pub operations: Vec<Operation>,
264 #[cfg(feature = "artifact-graph")]
266 pub artifact_graph: ArtifactGraph,
267 #[cfg(feature = "artifact-graph")]
269 #[serde(skip)]
270 pub scene_objects: Vec<Object>,
271 #[cfg(feature = "artifact-graph")]
274 #[serde(skip)]
275 pub source_range_to_object: BTreeMap<SourceRange, ObjectId>,
276 #[cfg(feature = "artifact-graph")]
277 #[serde(skip)]
278 pub var_solutions: Vec<(SourceRange, Number)>,
279 pub errors: Vec<CompilationError>,
282 pub filenames: IndexMap<ModuleId, ModulePath>,
284 pub default_planes: Option<DefaultPlanes>,
286}
287
288impl ExecOutcome {
289 pub fn scene_object_by_id(&self, id: ObjectId) -> Option<&Object> {
290 #[cfg(feature = "artifact-graph")]
291 {
292 debug_assert!(
293 id.0 < self.scene_objects.len(),
294 "Requested object ID {} but only have {} objects",
295 id.0,
296 self.scene_objects.len()
297 );
298 self.scene_objects.get(id.0)
299 }
300 #[cfg(not(feature = "artifact-graph"))]
301 {
302 let _ = id;
303 None
304 }
305 }
306
307 pub fn actual_errors(&self) -> impl Iterator<Item = &CompilationError> {
309 self.errors.iter().filter(|error| error.is_err())
310 }
311}
312
313#[derive(Debug, Clone, PartialEq, Eq)]
315pub struct MockConfig {
316 pub use_prev_memory: bool,
317 pub sketch_block_id: Option<ObjectId>,
320 pub freedom_analysis: bool,
323 #[cfg(feature = "artifact-graph")]
325 pub segment_ids_edited: AhashIndexSet<ObjectId>,
326}
327
328impl Default for MockConfig {
329 fn default() -> Self {
330 Self {
331 use_prev_memory: true,
333 sketch_block_id: None,
334 freedom_analysis: true,
335 #[cfg(feature = "artifact-graph")]
336 segment_ids_edited: AhashIndexSet::default(),
337 }
338 }
339}
340
341impl MockConfig {
342 pub fn new_sketch_mode(sketch_block_id: ObjectId) -> Self {
344 Self {
345 sketch_block_id: Some(sketch_block_id),
346 ..Default::default()
347 }
348 }
349
350 #[must_use]
351 pub(crate) fn no_freedom_analysis(mut self) -> Self {
352 self.freedom_analysis = false;
353 self
354 }
355}
356
357#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
358#[ts(export)]
359#[serde(rename_all = "camelCase")]
360pub struct DefaultPlanes {
361 pub xy: uuid::Uuid,
362 pub xz: uuid::Uuid,
363 pub yz: uuid::Uuid,
364 pub neg_xy: uuid::Uuid,
365 pub neg_xz: uuid::Uuid,
366 pub neg_yz: uuid::Uuid,
367}
368
369#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS)]
370#[ts(export)]
371#[serde(tag = "type", rename_all = "camelCase")]
372pub struct TagIdentifier {
373 pub value: String,
374 #[serde(skip)]
377 pub info: Vec<(usize, TagEngineInfo)>,
378 #[serde(skip)]
379 pub meta: Vec<Metadata>,
380}
381
382impl TagIdentifier {
383 pub fn get_info(&self, at_epoch: usize) -> Option<&TagEngineInfo> {
385 for (e, info) in self.info.iter().rev() {
386 if *e <= at_epoch {
387 return Some(info);
388 }
389 }
390
391 None
392 }
393
394 pub fn get_cur_info(&self) -> Option<&TagEngineInfo> {
396 self.info.last().map(|i| &i.1)
397 }
398
399 pub fn get_all_cur_info(&self) -> Vec<&TagEngineInfo> {
402 let Some(cur_epoch) = self.info.last().map(|(e, _)| *e) else {
403 return vec![];
404 };
405 self.info
406 .iter()
407 .rev()
408 .take_while(|(e, _)| *e == cur_epoch)
409 .map(|(_, info)| info)
410 .collect()
411 }
412
413 pub fn merge_info(&mut self, other: &TagIdentifier) {
415 assert_eq!(&self.value, &other.value);
416 for (oe, ot) in &other.info {
417 if let Some((e, t)) = self.info.last_mut() {
418 if *e > *oe {
420 continue;
421 }
422 if e == oe {
424 *t = ot.clone();
425 continue;
426 }
427 }
428 self.info.push((*oe, ot.clone()));
429 }
430 }
431
432 pub fn geometry(&self) -> Option<Geometry> {
433 self.get_cur_info().map(|info| info.geometry.clone())
434 }
435}
436
437impl Eq for TagIdentifier {}
438
439impl std::fmt::Display for TagIdentifier {
440 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
441 write!(f, "{}", self.value)
442 }
443}
444
445impl std::str::FromStr for TagIdentifier {
446 type Err = KclError;
447
448 fn from_str(s: &str) -> Result<Self, Self::Err> {
449 Ok(Self {
450 value: s.to_string(),
451 info: Vec::new(),
452 meta: Default::default(),
453 })
454 }
455}
456
457impl Ord for TagIdentifier {
458 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
459 self.value.cmp(&other.value)
460 }
461}
462
463impl PartialOrd for TagIdentifier {
464 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
465 Some(self.cmp(other))
466 }
467}
468
469impl std::hash::Hash for TagIdentifier {
470 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
471 self.value.hash(state);
472 }
473}
474
475#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
477#[ts(export)]
478#[serde(tag = "type", rename_all = "camelCase")]
479pub struct TagEngineInfo {
480 pub id: uuid::Uuid,
482 pub geometry: Geometry,
484 pub path: Option<Path>,
486 pub surface: Option<ExtrudeSurface>,
488}
489
490#[derive(Debug, Copy, Clone, Deserialize, Serialize, PartialEq)]
491pub enum BodyType {
492 Root,
493 Block,
494}
495
496#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, Eq, Copy)]
498#[ts(export)]
499#[serde(rename_all = "camelCase")]
500pub struct Metadata {
501 pub source_range: SourceRange,
503}
504
505impl From<Metadata> for Vec<SourceRange> {
506 fn from(meta: Metadata) -> Self {
507 vec![meta.source_range]
508 }
509}
510
511impl From<&Metadata> for SourceRange {
512 fn from(meta: &Metadata) -> Self {
513 meta.source_range
514 }
515}
516
517impl From<SourceRange> for Metadata {
518 fn from(source_range: SourceRange) -> Self {
519 Self { source_range }
520 }
521}
522
523impl<T> From<NodeRef<'_, T>> for Metadata {
524 fn from(node: NodeRef<'_, T>) -> Self {
525 Self {
526 source_range: SourceRange::new(node.start, node.end, node.module_id),
527 }
528 }
529}
530
531impl From<&Expr> for Metadata {
532 fn from(expr: &Expr) -> Self {
533 Self {
534 source_range: SourceRange::from(expr),
535 }
536 }
537}
538
539impl Metadata {
540 pub fn to_source_ref(meta: &[Metadata]) -> crate::front::SourceRef {
541 if meta.len() == 1 {
542 let meta = &meta[0];
543 return crate::front::SourceRef::Simple {
544 range: meta.source_range,
545 };
546 }
547 crate::front::SourceRef::BackTrace {
548 ranges: meta.iter().map(|m| m.source_range).collect(),
549 }
550 }
551}
552
553#[derive(PartialEq, Debug, Default, Clone)]
555pub enum ContextType {
556 #[default]
558 Live,
559
560 Mock,
564
565 MockCustomForwarded,
567}
568
569#[derive(Debug, Clone)]
573pub struct ExecutorContext {
574 pub engine: Arc<Box<dyn EngineManager>>,
575 pub fs: Arc<FileManager>,
576 pub settings: ExecutorSettings,
577 pub context_type: ContextType,
578}
579
580#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
582#[ts(export)]
583pub struct ExecutorSettings {
584 pub highlight_edges: bool,
586 pub enable_ssao: bool,
588 pub show_grid: bool,
590 pub replay: Option<String>,
593 pub project_directory: Option<TypedPath>,
596 pub current_file: Option<TypedPath>,
599 pub fixed_size_grid: bool,
601}
602
603impl Default for ExecutorSettings {
604 fn default() -> Self {
605 Self {
606 highlight_edges: true,
607 enable_ssao: false,
608 show_grid: false,
609 replay: None,
610 project_directory: None,
611 current_file: None,
612 fixed_size_grid: true,
613 }
614 }
615}
616
617impl From<crate::settings::types::Configuration> for ExecutorSettings {
618 fn from(config: crate::settings::types::Configuration) -> Self {
619 Self::from(config.settings)
620 }
621}
622
623impl From<crate::settings::types::Settings> for ExecutorSettings {
624 fn from(settings: crate::settings::types::Settings) -> Self {
625 Self {
626 highlight_edges: settings.modeling.highlight_edges.into(),
627 enable_ssao: settings.modeling.enable_ssao.into(),
628 show_grid: settings.modeling.show_scale_grid,
629 replay: None,
630 project_directory: None,
631 current_file: None,
632 fixed_size_grid: settings.modeling.fixed_size_grid,
633 }
634 }
635}
636
637impl From<crate::settings::types::project::ProjectConfiguration> for ExecutorSettings {
638 fn from(config: crate::settings::types::project::ProjectConfiguration) -> Self {
639 Self::from(config.settings.modeling)
640 }
641}
642
643impl From<crate::settings::types::ModelingSettings> for ExecutorSettings {
644 fn from(modeling: crate::settings::types::ModelingSettings) -> Self {
645 Self {
646 highlight_edges: modeling.highlight_edges.into(),
647 enable_ssao: modeling.enable_ssao.into(),
648 show_grid: modeling.show_scale_grid,
649 replay: None,
650 project_directory: None,
651 current_file: None,
652 fixed_size_grid: true,
653 }
654 }
655}
656
657impl From<crate::settings::types::project::ProjectModelingSettings> for ExecutorSettings {
658 fn from(modeling: crate::settings::types::project::ProjectModelingSettings) -> Self {
659 Self {
660 highlight_edges: modeling.highlight_edges.into(),
661 enable_ssao: modeling.enable_ssao.into(),
662 show_grid: Default::default(),
663 replay: None,
664 project_directory: None,
665 current_file: None,
666 fixed_size_grid: true,
667 }
668 }
669}
670
671impl ExecutorSettings {
672 pub fn with_current_file(&mut self, current_file: TypedPath) {
674 if current_file.extension() == Some("kcl") {
676 self.current_file = Some(current_file.clone());
677 if let Some(parent) = current_file.parent() {
679 self.project_directory = Some(parent);
680 } else {
681 self.project_directory = Some(TypedPath::from(""));
682 }
683 } else {
684 self.project_directory = Some(current_file);
685 }
686 }
687}
688
689impl ExecutorContext {
690 pub fn new_with_engine_and_fs(
692 engine: Arc<Box<dyn EngineManager>>,
693 fs: Arc<FileManager>,
694 settings: ExecutorSettings,
695 ) -> Self {
696 ExecutorContext {
697 engine,
698 fs,
699 settings,
700 context_type: ContextType::Live,
701 }
702 }
703
704 #[cfg(not(target_arch = "wasm32"))]
706 pub fn new_with_engine(engine: Arc<Box<dyn EngineManager>>, settings: ExecutorSettings) -> Self {
707 Self::new_with_engine_and_fs(engine, Arc::new(FileManager::new()), settings)
708 }
709
710 #[cfg(not(target_arch = "wasm32"))]
712 pub async fn new(client: &kittycad::Client, settings: ExecutorSettings) -> Result<Self> {
713 let pr = std::env::var("ZOO_ENGINE_PR").ok().and_then(|s| s.parse().ok());
714 let (ws, _headers) = client
715 .modeling()
716 .commands_ws(kittycad::modeling::CommandsWsParams {
717 api_call_id: None,
718 fps: None,
719 order_independent_transparency: None,
720 post_effect: if settings.enable_ssao {
721 Some(kittycad::types::PostEffectType::Ssao)
722 } else {
723 None
724 },
725 replay: settings.replay.clone(),
726 show_grid: if settings.show_grid { Some(true) } else { None },
727 pool: None,
728 pr,
729 unlocked_framerate: None,
730 webrtc: Some(false),
731 video_res_width: None,
732 video_res_height: None,
733 })
734 .await?;
735
736 let engine: Arc<Box<dyn EngineManager>> =
737 Arc::new(Box::new(crate::engine::conn::EngineConnection::new(ws).await?));
738
739 Ok(Self::new_with_engine(engine, settings))
740 }
741
742 #[cfg(target_arch = "wasm32")]
743 pub fn new(engine: Arc<Box<dyn EngineManager>>, fs: Arc<FileManager>, settings: ExecutorSettings) -> Self {
744 Self::new_with_engine_and_fs(engine, fs, settings)
745 }
746
747 #[cfg(not(target_arch = "wasm32"))]
748 pub async fn new_mock(settings: Option<ExecutorSettings>) -> Self {
749 ExecutorContext {
750 engine: Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().unwrap())),
751 fs: Arc::new(FileManager::new()),
752 settings: settings.unwrap_or_default(),
753 context_type: ContextType::Mock,
754 }
755 }
756
757 #[cfg(target_arch = "wasm32")]
758 pub fn new_mock(engine: Arc<Box<dyn EngineManager>>, fs: Arc<FileManager>, settings: ExecutorSettings) -> Self {
759 ExecutorContext {
760 engine,
761 fs,
762 settings,
763 context_type: ContextType::Mock,
764 }
765 }
766
767 #[cfg(target_arch = "wasm32")]
770 pub fn new_mock_for_lsp(
771 fs_manager: crate::fs::wasm::FileSystemManager,
772 settings: ExecutorSettings,
773 ) -> Result<Self, String> {
774 use crate::mock_engine;
775
776 let mock_engine = Arc::new(Box::new(
777 mock_engine::EngineConnection::new().map_err(|e| format!("Failed to create mock engine: {:?}", e))?,
778 ) as Box<dyn EngineManager>);
779
780 let fs = Arc::new(FileManager::new(fs_manager));
781
782 Ok(ExecutorContext {
783 engine: mock_engine,
784 fs,
785 settings,
786 context_type: ContextType::Mock,
787 })
788 }
789
790 #[cfg(not(target_arch = "wasm32"))]
791 pub fn new_forwarded_mock(engine: Arc<Box<dyn EngineManager>>) -> Self {
792 ExecutorContext {
793 engine,
794 fs: Arc::new(FileManager::new()),
795 settings: Default::default(),
796 context_type: ContextType::MockCustomForwarded,
797 }
798 }
799
800 #[cfg(not(target_arch = "wasm32"))]
806 pub async fn new_with_client(
807 settings: ExecutorSettings,
808 token: Option<String>,
809 engine_addr: Option<String>,
810 ) -> Result<Self> {
811 let client = crate::engine::new_zoo_client(token, engine_addr)?;
813
814 let ctx = Self::new(&client, settings).await?;
815 Ok(ctx)
816 }
817
818 #[cfg(not(target_arch = "wasm32"))]
823 pub async fn new_with_default_client() -> Result<Self> {
824 let ctx = Self::new_with_client(Default::default(), None, None).await?;
826 Ok(ctx)
827 }
828
829 #[cfg(not(target_arch = "wasm32"))]
831 pub async fn new_for_unit_test(engine_addr: Option<String>) -> Result<Self> {
832 let ctx = ExecutorContext::new_with_client(
833 ExecutorSettings {
834 highlight_edges: true,
835 enable_ssao: false,
836 show_grid: false,
837 replay: None,
838 project_directory: None,
839 current_file: None,
840 fixed_size_grid: false,
841 },
842 None,
843 engine_addr,
844 )
845 .await?;
846 Ok(ctx)
847 }
848
849 pub fn is_mock(&self) -> bool {
850 self.context_type == ContextType::Mock || self.context_type == ContextType::MockCustomForwarded
851 }
852
853 pub async fn no_engine_commands(&self) -> bool {
855 self.is_mock()
856 }
857
858 pub async fn send_clear_scene(
859 &self,
860 exec_state: &mut ExecState,
861 source_range: crate::execution::SourceRange,
862 ) -> Result<(), KclError> {
863 exec_state.mod_local.artifacts.clear();
866 exec_state.global.root_module_artifacts.clear();
867 exec_state.global.artifacts.clear();
868
869 self.engine
870 .clear_scene(&mut exec_state.mod_local.id_generator, source_range)
871 .await?;
872 if self.settings.enable_ssao {
875 let cmd_id = exec_state.next_uuid();
876 exec_state
877 .batch_modeling_cmd(
878 ModelingCmdMeta::with_id(exec_state, self, source_range, cmd_id),
879 ModelingCmd::from(mcmd::SetOrderIndependentTransparency::builder().enabled(false).build()),
880 )
881 .await?;
882 }
883 Ok(())
884 }
885
886 pub async fn bust_cache_and_reset_scene(&self) -> Result<ExecOutcome, KclErrorWithOutputs> {
887 cache::bust_cache().await;
888
889 let outcome = self.run_with_caching(crate::Program::empty()).await?;
894
895 Ok(outcome)
896 }
897
898 async fn prepare_mem(&self, exec_state: &mut ExecState) -> Result<(), KclErrorWithOutputs> {
899 self.eval_prelude(exec_state, SourceRange::synthetic())
900 .await
901 .map_err(KclErrorWithOutputs::no_outputs)?;
902 exec_state.mut_stack().push_new_root_env(true);
903 Ok(())
904 }
905
906 pub async fn run_mock(
907 &self,
908 program: &crate::Program,
909 mock_config: &MockConfig,
910 ) -> Result<ExecOutcome, KclErrorWithOutputs> {
911 assert!(
912 self.is_mock(),
913 "To use mock execution, instantiate via ExecutorContext::new_mock, not ::new"
914 );
915
916 let use_prev_memory = mock_config.use_prev_memory;
917 let mut exec_state = ExecState::new_mock(self, mock_config);
918 if use_prev_memory {
919 match cache::read_old_memory().await {
920 Some(mem) => {
921 *exec_state.mut_stack() = mem.stack;
922 exec_state.global.module_infos = mem.module_infos;
923 #[cfg(feature = "artifact-graph")]
924 {
925 let len = mock_config
926 .sketch_block_id
927 .map(|sketch_block_id| sketch_block_id.0)
928 .unwrap_or(0);
929 if let Some(scene_objects) = mem.scene_objects.get(0..len) {
930 exec_state
931 .global
932 .root_module_artifacts
933 .restore_scene_objects(scene_objects);
934 } else {
935 let message = format!(
936 "Cached scene objects length {} is less than expected length from cached object ID generator {}",
937 mem.scene_objects.len(),
938 len
939 );
940 debug_assert!(false, "{message}");
941 return Err(KclErrorWithOutputs::no_outputs(KclError::new_internal(
942 KclErrorDetails::new(message, vec![SourceRange::synthetic()]),
943 )));
944 }
945 }
946 }
947 None => self.prepare_mem(&mut exec_state).await?,
948 }
949 } else {
950 self.prepare_mem(&mut exec_state).await?
951 };
952
953 exec_state.mut_stack().push_new_env_for_scope();
956
957 let result = self.inner_run(program, &mut exec_state, PreserveMem::Always).await?;
958
959 let mut stack = exec_state.stack().clone();
964 let module_infos = exec_state.global.module_infos.clone();
965 #[cfg(feature = "artifact-graph")]
966 let scene_objects = exec_state.global.root_module_artifacts.scene_objects.clone();
967 #[cfg(not(feature = "artifact-graph"))]
968 let scene_objects = Default::default();
969 let outcome = exec_state.into_exec_outcome(result.0, self).await;
970
971 stack.squash_env(result.0);
972 let state = cache::SketchModeState {
973 stack,
974 module_infos,
975 scene_objects,
976 };
977 cache::write_old_memory(state).await;
978
979 Ok(outcome)
980 }
981
982 pub async fn run_with_caching(&self, program: crate::Program) -> Result<ExecOutcome, KclErrorWithOutputs> {
983 assert!(!self.is_mock());
984 let grid_scale = if self.settings.fixed_size_grid {
985 GridScaleBehavior::Fixed(program.meta_settings().ok().flatten().map(|s| s.default_length_units))
986 } else {
987 GridScaleBehavior::ScaleWithZoom
988 };
989
990 let original_program = program.clone();
991
992 let (_program, exec_state, result) = match cache::read_old_ast().await {
993 Some(mut cached_state) => {
994 let old = CacheInformation {
995 ast: &cached_state.main.ast,
996 settings: &cached_state.settings,
997 };
998 let new = CacheInformation {
999 ast: &program.ast,
1000 settings: &self.settings,
1001 };
1002
1003 let (clear_scene, program, import_check_info) = match cache::get_changed_program(old, new).await {
1005 CacheResult::ReExecute {
1006 clear_scene,
1007 reapply_settings,
1008 program: changed_program,
1009 } => {
1010 if reapply_settings
1011 && self
1012 .engine
1013 .reapply_settings(
1014 &self.settings,
1015 Default::default(),
1016 &mut cached_state.main.exec_state.id_generator,
1017 grid_scale,
1018 )
1019 .await
1020 .is_err()
1021 {
1022 (true, program, None)
1023 } else {
1024 (
1025 clear_scene,
1026 crate::Program {
1027 ast: changed_program,
1028 original_file_contents: program.original_file_contents,
1029 },
1030 None,
1031 )
1032 }
1033 }
1034 CacheResult::CheckImportsOnly {
1035 reapply_settings,
1036 ast: changed_program,
1037 } => {
1038 let mut reapply_failed = false;
1039 if reapply_settings {
1040 if self
1041 .engine
1042 .reapply_settings(
1043 &self.settings,
1044 Default::default(),
1045 &mut cached_state.main.exec_state.id_generator,
1046 grid_scale,
1047 )
1048 .await
1049 .is_ok()
1050 {
1051 cache::write_old_ast(GlobalState::with_settings(
1052 cached_state.clone(),
1053 self.settings.clone(),
1054 ))
1055 .await;
1056 } else {
1057 reapply_failed = true;
1058 }
1059 }
1060
1061 if reapply_failed {
1062 (true, program, None)
1063 } else {
1064 let mut new_exec_state = ExecState::new(self);
1066 let (new_universe, new_universe_map) =
1067 self.get_universe(&program, &mut new_exec_state).await?;
1068
1069 let clear_scene = new_universe.values().any(|value| {
1070 let id = value.1;
1071 match (
1072 cached_state.exec_state.get_source(id),
1073 new_exec_state.global.get_source(id),
1074 ) {
1075 (Some(s0), Some(s1)) => s0.source != s1.source,
1076 _ => false,
1077 }
1078 });
1079
1080 if !clear_scene {
1081 return Ok(cached_state.into_exec_outcome(self).await);
1083 }
1084
1085 (
1086 true,
1087 crate::Program {
1088 ast: changed_program,
1089 original_file_contents: program.original_file_contents,
1090 },
1091 Some((new_universe, new_universe_map, new_exec_state)),
1092 )
1093 }
1094 }
1095 CacheResult::NoAction(true) => {
1096 if self
1097 .engine
1098 .reapply_settings(
1099 &self.settings,
1100 Default::default(),
1101 &mut cached_state.main.exec_state.id_generator,
1102 grid_scale,
1103 )
1104 .await
1105 .is_ok()
1106 {
1107 cache::write_old_ast(GlobalState::with_settings(
1109 cached_state.clone(),
1110 self.settings.clone(),
1111 ))
1112 .await;
1113
1114 return Ok(cached_state.into_exec_outcome(self).await);
1115 }
1116 (true, program, None)
1117 }
1118 CacheResult::NoAction(false) => {
1119 return Ok(cached_state.into_exec_outcome(self).await);
1120 }
1121 };
1122
1123 let (exec_state, result) = match import_check_info {
1124 Some((new_universe, new_universe_map, mut new_exec_state)) => {
1125 self.send_clear_scene(&mut new_exec_state, Default::default())
1127 .await
1128 .map_err(KclErrorWithOutputs::no_outputs)?;
1129
1130 let result = self
1131 .run_concurrent(
1132 &program,
1133 &mut new_exec_state,
1134 Some((new_universe, new_universe_map)),
1135 PreserveMem::Normal,
1136 )
1137 .await;
1138
1139 (new_exec_state, result)
1140 }
1141 None if clear_scene => {
1142 let mut exec_state = cached_state.reconstitute_exec_state();
1144 exec_state.reset(self);
1145
1146 self.send_clear_scene(&mut exec_state, Default::default())
1147 .await
1148 .map_err(KclErrorWithOutputs::no_outputs)?;
1149
1150 let result = self
1151 .run_concurrent(&program, &mut exec_state, None, PreserveMem::Normal)
1152 .await;
1153
1154 (exec_state, result)
1155 }
1156 None => {
1157 let mut exec_state = cached_state.reconstitute_exec_state();
1158 exec_state.mut_stack().restore_env(cached_state.main.result_env);
1159
1160 let result = self
1161 .run_concurrent(&program, &mut exec_state, None, PreserveMem::Always)
1162 .await;
1163
1164 (exec_state, result)
1165 }
1166 };
1167
1168 (program, exec_state, result)
1169 }
1170 None => {
1171 let mut exec_state = ExecState::new(self);
1172 self.send_clear_scene(&mut exec_state, Default::default())
1173 .await
1174 .map_err(KclErrorWithOutputs::no_outputs)?;
1175
1176 let result = self
1177 .run_concurrent(&program, &mut exec_state, None, PreserveMem::Normal)
1178 .await;
1179
1180 (program, exec_state, result)
1181 }
1182 };
1183
1184 if result.is_err() {
1185 cache::bust_cache().await;
1186 }
1187
1188 let result = result?;
1190
1191 cache::write_old_ast(GlobalState::new(
1195 exec_state.clone(),
1196 self.settings.clone(),
1197 original_program.ast,
1198 result.0,
1199 ))
1200 .await;
1201
1202 let outcome = exec_state.into_exec_outcome(result.0, self).await;
1203 Ok(outcome)
1204 }
1205
1206 pub async fn run(
1210 &self,
1211 program: &crate::Program,
1212 exec_state: &mut ExecState,
1213 ) -> Result<(EnvironmentRef, Option<ModelingSessionData>), KclErrorWithOutputs> {
1214 self.run_concurrent(program, exec_state, None, PreserveMem::Normal)
1215 .await
1216 }
1217
1218 pub async fn run_concurrent(
1223 &self,
1224 program: &crate::Program,
1225 exec_state: &mut ExecState,
1226 universe_info: Option<(Universe, UniverseMap)>,
1227 preserve_mem: PreserveMem,
1228 ) -> Result<(EnvironmentRef, Option<ModelingSessionData>), KclErrorWithOutputs> {
1229 let (universe, universe_map) = if let Some((universe, universe_map)) = universe_info {
1232 (universe, universe_map)
1233 } else {
1234 self.get_universe(program, exec_state).await?
1235 };
1236
1237 let default_planes = self.engine.get_default_planes().read().await.clone();
1238
1239 self.eval_prelude(exec_state, SourceRange::synthetic())
1241 .await
1242 .map_err(KclErrorWithOutputs::no_outputs)?;
1243
1244 for modules in import_graph::import_graph(&universe, self)
1245 .map_err(|err| exec_state.error_with_outputs(err, None, default_planes.clone()))?
1246 .into_iter()
1247 {
1248 #[cfg(not(target_arch = "wasm32"))]
1249 let mut set = tokio::task::JoinSet::new();
1250
1251 #[allow(clippy::type_complexity)]
1252 let (results_tx, mut results_rx): (
1253 tokio::sync::mpsc::Sender<(ModuleId, ModulePath, Result<ModuleRepr, KclError>)>,
1254 tokio::sync::mpsc::Receiver<_>,
1255 ) = tokio::sync::mpsc::channel(1);
1256
1257 for module in modules {
1258 let Some((import_stmt, module_id, module_path, repr)) = universe.get(&module) else {
1259 return Err(KclErrorWithOutputs::no_outputs(KclError::new_internal(
1260 KclErrorDetails::new(format!("Module {module} not found in universe"), Default::default()),
1261 )));
1262 };
1263 let module_id = *module_id;
1264 let module_path = module_path.clone();
1265 let source_range = SourceRange::from(import_stmt);
1266 let module_exec_state = exec_state.clone();
1268
1269 self.add_import_module_ops(
1270 exec_state,
1271 &program.ast,
1272 module_id,
1273 &module_path,
1274 source_range,
1275 &universe_map,
1276 );
1277
1278 let repr = repr.clone();
1279 let exec_ctxt = self.clone();
1280 let results_tx = results_tx.clone();
1281
1282 let exec_module = async |exec_ctxt: &ExecutorContext,
1283 repr: &ModuleRepr,
1284 module_id: ModuleId,
1285 module_path: &ModulePath,
1286 exec_state: &mut ExecState,
1287 source_range: SourceRange|
1288 -> Result<ModuleRepr, KclError> {
1289 match repr {
1290 ModuleRepr::Kcl(program, _) => {
1291 let result = exec_ctxt
1292 .exec_module_from_ast(
1293 program,
1294 module_id,
1295 module_path,
1296 exec_state,
1297 source_range,
1298 PreserveMem::Normal,
1299 )
1300 .await;
1301
1302 result.map(|val| ModuleRepr::Kcl(program.clone(), Some(val)))
1303 }
1304 ModuleRepr::Foreign(geom, _) => {
1305 let result = crate::execution::import::send_to_engine(geom.clone(), exec_state, exec_ctxt)
1306 .await
1307 .map(|geom| Some(KclValue::ImportedGeometry(geom)));
1308
1309 result.map(|val| {
1310 ModuleRepr::Foreign(geom.clone(), Some((val, exec_state.mod_local.artifacts.clone())))
1311 })
1312 }
1313 ModuleRepr::Dummy | ModuleRepr::Root => Err(KclError::new_internal(KclErrorDetails::new(
1314 format!("Module {module_path} not found in universe"),
1315 vec![source_range],
1316 ))),
1317 }
1318 };
1319
1320 #[cfg(target_arch = "wasm32")]
1321 {
1322 wasm_bindgen_futures::spawn_local(async move {
1323 let mut exec_state = module_exec_state;
1324 let exec_ctxt = exec_ctxt;
1325
1326 let result = exec_module(
1327 &exec_ctxt,
1328 &repr,
1329 module_id,
1330 &module_path,
1331 &mut exec_state,
1332 source_range,
1333 )
1334 .await;
1335
1336 results_tx
1337 .send((module_id, module_path, result))
1338 .await
1339 .unwrap_or_default();
1340 });
1341 }
1342 #[cfg(not(target_arch = "wasm32"))]
1343 {
1344 set.spawn(async move {
1345 let mut exec_state = module_exec_state;
1346 let exec_ctxt = exec_ctxt;
1347
1348 let result = exec_module(
1349 &exec_ctxt,
1350 &repr,
1351 module_id,
1352 &module_path,
1353 &mut exec_state,
1354 source_range,
1355 )
1356 .await;
1357
1358 results_tx
1359 .send((module_id, module_path, result))
1360 .await
1361 .unwrap_or_default();
1362 });
1363 }
1364 }
1365
1366 drop(results_tx);
1367
1368 while let Some((module_id, _, result)) = results_rx.recv().await {
1369 match result {
1370 Ok(new_repr) => {
1371 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
1372
1373 match &mut repr {
1374 ModuleRepr::Kcl(_, cache) => {
1375 let ModuleRepr::Kcl(_, session_data) = new_repr else {
1376 unreachable!();
1377 };
1378 *cache = session_data;
1379 }
1380 ModuleRepr::Foreign(_, cache) => {
1381 let ModuleRepr::Foreign(_, session_data) = new_repr else {
1382 unreachable!();
1383 };
1384 *cache = session_data;
1385 }
1386 ModuleRepr::Dummy | ModuleRepr::Root => unreachable!(),
1387 }
1388
1389 exec_state.global.module_infos[&module_id].restore_repr(repr);
1390 }
1391 Err(e) => {
1392 return Err(exec_state.error_with_outputs(e, None, default_planes));
1393 }
1394 }
1395 }
1396 }
1397
1398 exec_state
1402 .global
1403 .root_module_artifacts
1404 .extend(std::mem::take(&mut exec_state.mod_local.artifacts));
1405
1406 self.inner_run(program, exec_state, preserve_mem).await
1407 }
1408
1409 async fn get_universe(
1412 &self,
1413 program: &crate::Program,
1414 exec_state: &mut ExecState,
1415 ) -> Result<(Universe, UniverseMap), KclErrorWithOutputs> {
1416 exec_state.add_root_module_contents(program);
1417
1418 let mut universe = std::collections::HashMap::new();
1419
1420 let default_planes = self.engine.get_default_planes().read().await.clone();
1421
1422 let root_imports = import_graph::import_universe(
1423 self,
1424 &ModulePath::Main,
1425 &ModuleRepr::Kcl(program.ast.clone(), None),
1426 &mut universe,
1427 exec_state,
1428 )
1429 .await
1430 .map_err(|err| exec_state.error_with_outputs(err, None, default_planes))?;
1431
1432 Ok((universe, root_imports))
1433 }
1434
1435 #[cfg(not(feature = "artifact-graph"))]
1436 fn add_import_module_ops(
1437 &self,
1438 _exec_state: &mut ExecState,
1439 _program: &crate::parsing::ast::types::Node<crate::parsing::ast::types::Program>,
1440 _module_id: ModuleId,
1441 _module_path: &ModulePath,
1442 _source_range: SourceRange,
1443 _universe_map: &UniverseMap,
1444 ) {
1445 }
1446
1447 #[cfg(feature = "artifact-graph")]
1448 fn add_import_module_ops(
1449 &self,
1450 exec_state: &mut ExecState,
1451 program: &crate::parsing::ast::types::Node<crate::parsing::ast::types::Program>,
1452 module_id: ModuleId,
1453 module_path: &ModulePath,
1454 source_range: SourceRange,
1455 universe_map: &UniverseMap,
1456 ) {
1457 match module_path {
1458 ModulePath::Main => {
1459 }
1461 ModulePath::Local {
1462 value,
1463 original_import_path,
1464 } => {
1465 if universe_map.contains_key(value) {
1468 use crate::NodePath;
1469
1470 let node_path = if source_range.is_top_level_module() {
1471 let cached_body_items = exec_state.global.artifacts.cached_body_items();
1472 NodePath::from_range(
1473 &exec_state.build_program_lookup(program.clone()),
1474 cached_body_items,
1475 source_range,
1476 )
1477 .unwrap_or_default()
1478 } else {
1479 NodePath::placeholder()
1482 };
1483
1484 let name = match original_import_path {
1485 Some(value) => value.to_string_lossy(),
1486 None => value.file_name().unwrap_or_default(),
1487 };
1488 exec_state.push_op(Operation::GroupBegin {
1489 group: Group::ModuleInstance { name, module_id },
1490 node_path,
1491 source_range,
1492 });
1493 exec_state.push_op(Operation::GroupEnd);
1497 }
1498 }
1499 ModulePath::Std { .. } => {
1500 }
1502 }
1503 }
1504
1505 async fn inner_run(
1508 &self,
1509 program: &crate::Program,
1510 exec_state: &mut ExecState,
1511 preserve_mem: PreserveMem,
1512 ) -> Result<(EnvironmentRef, Option<ModelingSessionData>), KclErrorWithOutputs> {
1513 let _stats = crate::log::LogPerfStats::new("Interpretation");
1514
1515 let grid_scale = if self.settings.fixed_size_grid {
1517 GridScaleBehavior::Fixed(program.meta_settings().ok().flatten().map(|s| s.default_length_units))
1518 } else {
1519 GridScaleBehavior::ScaleWithZoom
1520 };
1521 self.engine
1522 .reapply_settings(
1523 &self.settings,
1524 Default::default(),
1525 exec_state.id_generator(),
1526 grid_scale,
1527 )
1528 .await
1529 .map_err(KclErrorWithOutputs::no_outputs)?;
1530
1531 let default_planes = self.engine.get_default_planes().read().await.clone();
1532 let result = self
1533 .execute_and_build_graph(&program.ast, exec_state, preserve_mem)
1534 .await;
1535
1536 crate::log::log(format!(
1537 "Post interpretation KCL memory stats: {:#?}",
1538 exec_state.stack().memory.stats
1539 ));
1540 crate::log::log(format!("Engine stats: {:?}", self.engine.stats()));
1541
1542 async fn write_old_memory(ctx: &ExecutorContext, exec_state: &ExecState, env_ref: EnvironmentRef) {
1545 if ctx.is_mock() {
1546 return;
1547 }
1548 let mut stack = exec_state.stack().deep_clone();
1549 stack.restore_env(env_ref);
1550 let state = cache::SketchModeState {
1551 stack,
1552 module_infos: exec_state.global.module_infos.clone(),
1553 #[cfg(feature = "artifact-graph")]
1554 scene_objects: exec_state.global.root_module_artifacts.scene_objects.clone(),
1555 #[cfg(not(feature = "artifact-graph"))]
1556 scene_objects: Default::default(),
1557 };
1558 cache::write_old_memory(state).await;
1559 }
1560
1561 let env_ref = match result {
1562 Ok(env_ref) => env_ref,
1563 Err((err, env_ref)) => {
1564 if let Some(env_ref) = env_ref {
1567 write_old_memory(self, exec_state, env_ref).await;
1568 }
1569 return Err(exec_state.error_with_outputs(err, env_ref, default_planes));
1570 }
1571 };
1572
1573 write_old_memory(self, exec_state, env_ref).await;
1574
1575 let session_data = self.engine.get_session_data().await;
1576
1577 Ok((env_ref, session_data))
1578 }
1579
1580 async fn execute_and_build_graph(
1583 &self,
1584 program: NodeRef<'_, crate::parsing::ast::types::Program>,
1585 exec_state: &mut ExecState,
1586 preserve_mem: PreserveMem,
1587 ) -> Result<EnvironmentRef, (KclError, Option<EnvironmentRef>)> {
1588 #[cfg(feature = "artifact-graph")]
1594 let start_op = exec_state.global.root_module_artifacts.operations.len();
1595
1596 self.eval_prelude(exec_state, SourceRange::from(program).start_as_range())
1597 .await
1598 .map_err(|e| (e, None))?;
1599
1600 let exec_result = self
1601 .exec_module_body(
1602 program,
1603 exec_state,
1604 preserve_mem,
1605 ModuleId::default(),
1606 &ModulePath::Main,
1607 )
1608 .await
1609 .map(
1610 |ModuleExecutionOutcome {
1611 environment: env_ref,
1612 artifacts: module_artifacts,
1613 ..
1614 }| {
1615 exec_state.global.root_module_artifacts.extend(module_artifacts);
1618 env_ref
1619 },
1620 )
1621 .map_err(|(err, env_ref, module_artifacts)| {
1622 if let Some(module_artifacts) = module_artifacts {
1623 exec_state.global.root_module_artifacts.extend(module_artifacts);
1626 }
1627 (err, env_ref)
1628 });
1629
1630 #[cfg(feature = "artifact-graph")]
1631 {
1632 let programs = &exec_state.build_program_lookup(program.clone());
1634 let cached_body_items = exec_state.global.artifacts.cached_body_items();
1635 for op in exec_state
1636 .global
1637 .root_module_artifacts
1638 .operations
1639 .iter_mut()
1640 .skip(start_op)
1641 {
1642 op.fill_node_paths(programs, cached_body_items);
1643 }
1644 for module in exec_state.global.module_infos.values_mut() {
1645 if let ModuleRepr::Kcl(_, Some(outcome)) = &mut module.repr {
1646 for op in &mut outcome.artifacts.operations {
1647 op.fill_node_paths(programs, cached_body_items);
1648 }
1649 }
1650 }
1651 }
1652
1653 self.engine.ensure_async_commands_completed().await.map_err(|e| {
1655 match &exec_result {
1656 Ok(env_ref) => (e, Some(*env_ref)),
1657 Err((exec_err, env_ref)) => (exec_err.clone(), *env_ref),
1659 }
1660 })?;
1661
1662 self.engine.clear_queues().await;
1665
1666 match exec_state.build_artifact_graph(&self.engine, program).await {
1667 Ok(_) => exec_result,
1668 Err(err) => exec_result.and_then(|env_ref| Err((err, Some(env_ref)))),
1669 }
1670 }
1671
1672 async fn eval_prelude(&self, exec_state: &mut ExecState, source_range: SourceRange) -> Result<(), KclError> {
1676 if exec_state.stack().memory.requires_std() {
1677 #[cfg(feature = "artifact-graph")]
1678 let initial_ops = exec_state.mod_local.artifacts.operations.len();
1679
1680 let path = vec!["std".to_owned(), "prelude".to_owned()];
1681 let resolved_path = ModulePath::from_std_import_path(&path)?;
1682 let id = self
1683 .open_module(&ImportPath::Std { path }, &[], &resolved_path, exec_state, source_range)
1684 .await?;
1685 let (module_memory, _) = self.exec_module_for_items(id, exec_state, source_range).await?;
1686
1687 exec_state.mut_stack().memory.set_std(module_memory);
1688
1689 #[cfg(feature = "artifact-graph")]
1695 exec_state.mod_local.artifacts.operations.truncate(initial_ops);
1696 }
1697
1698 Ok(())
1699 }
1700
1701 pub async fn prepare_snapshot(&self) -> std::result::Result<TakeSnapshot, ExecError> {
1703 self.engine
1705 .send_modeling_cmd(
1706 uuid::Uuid::new_v4(),
1707 crate::execution::SourceRange::default(),
1708 &ModelingCmd::from(
1709 mcmd::ZoomToFit::builder()
1710 .object_ids(Default::default())
1711 .animated(false)
1712 .padding(0.1)
1713 .build(),
1714 ),
1715 )
1716 .await
1717 .map_err(KclErrorWithOutputs::no_outputs)?;
1718
1719 let resp = self
1721 .engine
1722 .send_modeling_cmd(
1723 uuid::Uuid::new_v4(),
1724 crate::execution::SourceRange::default(),
1725 &ModelingCmd::from(mcmd::TakeSnapshot::builder().format(ImageFormat::Png).build()),
1726 )
1727 .await
1728 .map_err(KclErrorWithOutputs::no_outputs)?;
1729
1730 let OkWebSocketResponseData::Modeling {
1731 modeling_response: OkModelingCmdResponse::TakeSnapshot(contents),
1732 } = resp
1733 else {
1734 return Err(ExecError::BadPng(format!(
1735 "Instead of a TakeSnapshot response, the engine returned {resp:?}"
1736 )));
1737 };
1738 Ok(contents)
1739 }
1740
1741 pub async fn export(
1743 &self,
1744 format: kittycad_modeling_cmds::format::OutputFormat3d,
1745 ) -> Result<Vec<kittycad_modeling_cmds::websocket::RawFile>, KclError> {
1746 let resp = self
1747 .engine
1748 .send_modeling_cmd(
1749 uuid::Uuid::new_v4(),
1750 crate::SourceRange::default(),
1751 &kittycad_modeling_cmds::ModelingCmd::Export(
1752 kittycad_modeling_cmds::Export::builder()
1753 .entity_ids(vec![])
1754 .format(format)
1755 .build(),
1756 ),
1757 )
1758 .await?;
1759
1760 let kittycad_modeling_cmds::websocket::OkWebSocketResponseData::Export { files } = resp else {
1761 return Err(KclError::new_internal(crate::errors::KclErrorDetails::new(
1762 format!("Expected Export response, got {resp:?}",),
1763 vec![SourceRange::default()],
1764 )));
1765 };
1766
1767 Ok(files)
1768 }
1769
1770 pub async fn export_step(
1772 &self,
1773 deterministic_time: bool,
1774 ) -> Result<Vec<kittycad_modeling_cmds::websocket::RawFile>, KclError> {
1775 let files = self
1776 .export(kittycad_modeling_cmds::format::OutputFormat3d::Step(
1777 kittycad_modeling_cmds::format::step::export::Options::builder()
1778 .coords(*kittycad_modeling_cmds::coord::KITTYCAD)
1779 .maybe_created(if deterministic_time {
1780 Some("2021-01-01T00:00:00Z".parse().map_err(|e| {
1781 KclError::new_internal(crate::errors::KclErrorDetails::new(
1782 format!("Failed to parse date: {e}"),
1783 vec![SourceRange::default()],
1784 ))
1785 })?)
1786 } else {
1787 None
1788 })
1789 .build(),
1790 ))
1791 .await?;
1792
1793 Ok(files)
1794 }
1795
1796 pub async fn close(&self) {
1797 self.engine.close().await;
1798 }
1799}
1800
1801#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Ord, PartialOrd, Hash, ts_rs::TS)]
1802pub struct ArtifactId(Uuid);
1803
1804impl ArtifactId {
1805 pub fn new(uuid: Uuid) -> Self {
1806 Self(uuid)
1807 }
1808
1809 pub fn placeholder() -> Self {
1811 Self(Uuid::nil())
1812 }
1813}
1814
1815impl From<Uuid> for ArtifactId {
1816 fn from(uuid: Uuid) -> Self {
1817 Self::new(uuid)
1818 }
1819}
1820
1821impl From<&Uuid> for ArtifactId {
1822 fn from(uuid: &Uuid) -> Self {
1823 Self::new(*uuid)
1824 }
1825}
1826
1827impl From<ArtifactId> for Uuid {
1828 fn from(id: ArtifactId) -> Self {
1829 id.0
1830 }
1831}
1832
1833impl From<&ArtifactId> for Uuid {
1834 fn from(id: &ArtifactId) -> Self {
1835 id.0
1836 }
1837}
1838
1839impl From<ModelingCmdId> for ArtifactId {
1840 fn from(id: ModelingCmdId) -> Self {
1841 Self::new(*id.as_ref())
1842 }
1843}
1844
1845impl From<&ModelingCmdId> for ArtifactId {
1846 fn from(id: &ModelingCmdId) -> Self {
1847 Self::new(*id.as_ref())
1848 }
1849}
1850
1851#[cfg(test)]
1852pub(crate) async fn parse_execute(code: &str) -> Result<ExecTestResults, KclError> {
1853 parse_execute_with_project_dir(code, None).await
1854}
1855
1856#[cfg(test)]
1857pub(crate) async fn parse_execute_with_project_dir(
1858 code: &str,
1859 project_directory: Option<TypedPath>,
1860) -> Result<ExecTestResults, KclError> {
1861 let program = crate::Program::parse_no_errs(code)?;
1862
1863 let exec_ctxt = ExecutorContext {
1864 engine: Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().map_err(
1865 |err| {
1866 KclError::new_internal(crate::errors::KclErrorDetails::new(
1867 format!("Failed to create mock engine connection: {err}"),
1868 vec![SourceRange::default()],
1869 ))
1870 },
1871 )?)),
1872 fs: Arc::new(crate::fs::FileManager::new()),
1873 settings: ExecutorSettings {
1874 project_directory,
1875 ..Default::default()
1876 },
1877 context_type: ContextType::Mock,
1878 };
1879 let mut exec_state = ExecState::new(&exec_ctxt);
1880 let result = exec_ctxt.run(&program, &mut exec_state).await?;
1881
1882 Ok(ExecTestResults {
1883 program,
1884 mem_env: result.0,
1885 exec_ctxt,
1886 exec_state,
1887 })
1888}
1889
1890#[cfg(test)]
1891#[derive(Debug)]
1892pub(crate) struct ExecTestResults {
1893 program: crate::Program,
1894 mem_env: EnvironmentRef,
1895 exec_ctxt: ExecutorContext,
1896 exec_state: ExecState,
1897}
1898
1899#[cfg(feature = "artifact-graph")]
1903pub struct ProgramLookup {
1904 programs: IndexMap<ModuleId, crate::parsing::ast::types::Node<crate::parsing::ast::types::Program>>,
1905}
1906
1907#[cfg(feature = "artifact-graph")]
1908impl ProgramLookup {
1909 pub fn new(
1912 current: crate::parsing::ast::types::Node<crate::parsing::ast::types::Program>,
1913 module_infos: state::ModuleInfoMap,
1914 ) -> Self {
1915 let mut programs = IndexMap::with_capacity(module_infos.len());
1916 for (id, info) in module_infos {
1917 if let ModuleRepr::Kcl(program, _) = info.repr {
1918 programs.insert(id, program);
1919 }
1920 }
1921 programs.insert(ModuleId::default(), current);
1922 Self { programs }
1923 }
1924
1925 pub fn program_for_module(
1926 &self,
1927 module_id: ModuleId,
1928 ) -> Option<&crate::parsing::ast::types::Node<crate::parsing::ast::types::Program>> {
1929 self.programs.get(&module_id)
1930 }
1931}
1932
1933#[cfg(test)]
1934mod tests {
1935 use pretty_assertions::assert_eq;
1936
1937 use super::*;
1938 use crate::ModuleId;
1939 use crate::errors::KclErrorDetails;
1940 use crate::errors::Severity;
1941 use crate::exec::NumericType;
1942 use crate::execution::memory::Stack;
1943 use crate::execution::types::RuntimeType;
1944
1945 #[track_caller]
1947 fn mem_get_json(memory: &Stack, env: EnvironmentRef, name: &str) -> KclValue {
1948 memory.memory.get_from_unchecked(name, env).unwrap().to_owned()
1949 }
1950
1951 #[tokio::test(flavor = "multi_thread")]
1952 async fn test_execute_warn() {
1953 let text = "@blah";
1954 let result = parse_execute(text).await.unwrap();
1955 let errs = result.exec_state.errors();
1956 assert_eq!(errs.len(), 1);
1957 assert_eq!(errs[0].severity, crate::errors::Severity::Warning);
1958 assert!(
1959 errs[0].message.contains("Unknown annotation"),
1960 "unexpected warning message: {}",
1961 errs[0].message
1962 );
1963 }
1964
1965 #[tokio::test(flavor = "multi_thread")]
1966 async fn test_execute_fn_definitions() {
1967 let ast = r#"fn def(@x) {
1968 return x
1969}
1970fn ghi(@x) {
1971 return x
1972}
1973fn jkl(@x) {
1974 return x
1975}
1976fn hmm(@x) {
1977 return x
1978}
1979
1980yo = 5 + 6
1981
1982abc = 3
1983identifierGuy = 5
1984part001 = startSketchOn(XY)
1985|> startProfile(at = [-1.2, 4.83])
1986|> line(end = [2.8, 0])
1987|> angledLine(angle = 100 + 100, length = 3.01)
1988|> angledLine(angle = abc, length = 3.02)
1989|> angledLine(angle = def(yo), length = 3.03)
1990|> angledLine(angle = ghi(2), length = 3.04)
1991|> angledLine(angle = jkl(yo) + 2, length = 3.05)
1992|> close()
1993yo2 = hmm([identifierGuy + 5])"#;
1994
1995 parse_execute(ast).await.unwrap();
1996 }
1997
1998 #[tokio::test(flavor = "multi_thread")]
1999 async fn multiple_sketch_blocks_do_not_reuse_on_cache_name() {
2000 let code = r#"@settings(experimentalFeatures = allow)
2001firstProfile = sketch(on = XY) {
2002 edge1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
2003 edge2 = line(start = [var 4mm, var 0mm], end = [var 4mm, var 3mm])
2004 edge3 = line(start = [var 4mm, var 3mm], end = [var 0mm, var 3mm])
2005 edge4 = line(start = [var 0mm, var 3mm], end = [var 0mm, var 0mm])
2006 coincident([edge1.end, edge2.start])
2007 coincident([edge2.end, edge3.start])
2008 coincident([edge3.end, edge4.start])
2009 coincident([edge4.end, edge1.start])
2010}
2011
2012secondProfile = sketch(on = offsetPlane(XY, offset = 6mm)) {
2013 edge5 = line(start = [var 1mm, var 1mm], end = [var 5mm, var 1mm])
2014 edge6 = line(start = [var 5mm, var 1mm], end = [var 5mm, var 4mm])
2015 edge7 = line(start = [var 5mm, var 4mm], end = [var 1mm, var 4mm])
2016 edge8 = line(start = [var 1mm, var 4mm], end = [var 1mm, var 1mm])
2017 coincident([edge5.end, edge6.start])
2018 coincident([edge6.end, edge7.start])
2019 coincident([edge7.end, edge8.start])
2020 coincident([edge8.end, edge5.start])
2021}
2022
2023firstSolid = extrude(region(point = [2mm, 1mm], sketch = firstProfile), length = 2mm)
2024secondSolid = extrude(region(point = [2mm, 2mm], sketch = secondProfile), length = 2mm)
2025"#;
2026
2027 let result = parse_execute(code).await.unwrap();
2028 assert!(result.exec_state.errors().is_empty());
2029 }
2030
2031 #[tokio::test(flavor = "multi_thread")]
2032 async fn issue_10639_blend_example_with_two_sketch_blocks_executes() {
2033 let code = r#"@settings(experimentalFeatures = allow)
2034sketch001 = sketch(on = YZ) {
2035 line1 = line(start = [var 4.1mm, var -0.1mm], end = [var 5.5mm, var 0mm])
2036 line2 = line(start = [var 5.5mm, var 0mm], end = [var 5.5mm, var 3mm])
2037 line3 = line(start = [var 5.5mm, var 3mm], end = [var 3.9mm, var 2.8mm])
2038 line4 = line(start = [var 4.1mm, var 3mm], end = [var 4.5mm, var -0.2mm])
2039 coincident([line1.end, line2.start])
2040 coincident([line2.end, line3.start])
2041 coincident([line3.end, line4.start])
2042 coincident([line4.end, line1.start])
2043}
2044
2045sketch002 = sketch(on = -XZ) {
2046 line5 = line(start = [var -5.3mm, var -0.1mm], end = [var -3.5mm, var -0.1mm])
2047 line6 = line(start = [var -3.5mm, var -0.1mm], end = [var -3.5mm, var 3.1mm])
2048 line7 = line(start = [var -3.5mm, var 4.5mm], end = [var -5.4mm, var 4.5mm])
2049 line8 = line(start = [var -5.3mm, var 3.1mm], end = [var -5.3mm, var -0.1mm])
2050 coincident([line5.end, line6.start])
2051 coincident([line6.end, line7.start])
2052 coincident([line7.end, line8.start])
2053 coincident([line8.end, line5.start])
2054}
2055
2056region001 = region(point = [-4.4mm, 2mm], sketch = sketch002)
2057extrude001 = extrude(region001, length = -2mm, bodyType = SURFACE)
2058region002 = region(point = [4.8mm, 1.5mm], sketch = sketch001)
2059extrude002 = extrude(region002, length = -2mm, bodyType = SURFACE)
2060
2061myBlend = blend([extrude001.sketch.tags.line7, extrude002.sketch.tags.line3])
2062"#;
2063
2064 let result = parse_execute(code).await.unwrap();
2065 assert!(result.exec_state.errors().is_empty());
2066 }
2067
2068 #[tokio::test(flavor = "multi_thread")]
2069 async fn issue_10741_point_circle_coincident_executes() {
2070 let code = r#"@settings(experimentalFeatures = allow)
2071sketch001 = sketch(on = YZ) {
2072 circle1 = circle(start = [var -2.67mm, var 1.8mm], center = [var -1.53mm, var 0.78mm])
2073 line1 = line(start = [var -1.05mm, var 2.22mm], end = [var -3.58mm, var -0.78mm])
2074 coincident([line1.start, circle1])
2075}
2076"#;
2077
2078 let result = parse_execute(code).await.unwrap();
2079 assert!(
2080 result
2081 .exec_state
2082 .errors()
2083 .iter()
2084 .all(|error| error.severity != Severity::Error),
2085 "unexpected execution errors: {:#?}",
2086 result.exec_state.errors()
2087 );
2088 }
2089
2090 #[tokio::test(flavor = "multi_thread")]
2091 async fn test_execute_with_pipe_substitutions_unary() {
2092 let ast = r#"myVar = 3
2093part001 = startSketchOn(XY)
2094 |> startProfile(at = [0, 0])
2095 |> line(end = [3, 4], tag = $seg01)
2096 |> line(end = [
2097 min([segLen(seg01), myVar]),
2098 -legLen(hypotenuse = segLen(seg01), leg = myVar)
2099])
2100"#;
2101
2102 parse_execute(ast).await.unwrap();
2103 }
2104
2105 #[tokio::test(flavor = "multi_thread")]
2106 async fn test_execute_with_pipe_substitutions() {
2107 let ast = r#"myVar = 3
2108part001 = startSketchOn(XY)
2109 |> startProfile(at = [0, 0])
2110 |> line(end = [3, 4], tag = $seg01)
2111 |> line(end = [
2112 min([segLen(seg01), myVar]),
2113 legLen(hypotenuse = segLen(seg01), leg = myVar)
2114])
2115"#;
2116
2117 parse_execute(ast).await.unwrap();
2118 }
2119
2120 #[tokio::test(flavor = "multi_thread")]
2121 async fn test_execute_with_inline_comment() {
2122 let ast = r#"baseThick = 1
2123armAngle = 60
2124
2125baseThickHalf = baseThick / 2
2126halfArmAngle = armAngle / 2
2127
2128arrExpShouldNotBeIncluded = [1, 2, 3]
2129objExpShouldNotBeIncluded = { a = 1, b = 2, c = 3 }
2130
2131part001 = startSketchOn(XY)
2132 |> startProfile(at = [0, 0])
2133 |> yLine(endAbsolute = 1)
2134 |> xLine(length = 3.84) // selection-range-7ish-before-this
2135
2136variableBelowShouldNotBeIncluded = 3
2137"#;
2138
2139 parse_execute(ast).await.unwrap();
2140 }
2141
2142 #[tokio::test(flavor = "multi_thread")]
2143 async fn test_execute_with_function_literal_in_pipe() {
2144 let ast = r#"w = 20
2145l = 8
2146h = 10
2147
2148fn thing() {
2149 return -8
2150}
2151
2152firstExtrude = startSketchOn(XY)
2153 |> startProfile(at = [0,0])
2154 |> line(end = [0, l])
2155 |> line(end = [w, 0])
2156 |> line(end = [0, thing()])
2157 |> close()
2158 |> extrude(length = h)"#;
2159
2160 parse_execute(ast).await.unwrap();
2161 }
2162
2163 #[tokio::test(flavor = "multi_thread")]
2164 async fn test_execute_with_function_unary_in_pipe() {
2165 let ast = r#"w = 20
2166l = 8
2167h = 10
2168
2169fn thing(@x) {
2170 return -x
2171}
2172
2173firstExtrude = startSketchOn(XY)
2174 |> startProfile(at = [0,0])
2175 |> line(end = [0, l])
2176 |> line(end = [w, 0])
2177 |> line(end = [0, thing(8)])
2178 |> close()
2179 |> extrude(length = h)"#;
2180
2181 parse_execute(ast).await.unwrap();
2182 }
2183
2184 #[tokio::test(flavor = "multi_thread")]
2185 async fn test_execute_with_function_array_in_pipe() {
2186 let ast = r#"w = 20
2187l = 8
2188h = 10
2189
2190fn thing(@x) {
2191 return [0, -x]
2192}
2193
2194firstExtrude = startSketchOn(XY)
2195 |> startProfile(at = [0,0])
2196 |> line(end = [0, l])
2197 |> line(end = [w, 0])
2198 |> line(end = thing(8))
2199 |> close()
2200 |> extrude(length = h)"#;
2201
2202 parse_execute(ast).await.unwrap();
2203 }
2204
2205 #[tokio::test(flavor = "multi_thread")]
2206 async fn test_execute_with_function_call_in_pipe() {
2207 let ast = r#"w = 20
2208l = 8
2209h = 10
2210
2211fn other_thing(@y) {
2212 return -y
2213}
2214
2215fn thing(@x) {
2216 return other_thing(x)
2217}
2218
2219firstExtrude = startSketchOn(XY)
2220 |> startProfile(at = [0,0])
2221 |> line(end = [0, l])
2222 |> line(end = [w, 0])
2223 |> line(end = [0, thing(8)])
2224 |> close()
2225 |> extrude(length = h)"#;
2226
2227 parse_execute(ast).await.unwrap();
2228 }
2229
2230 #[tokio::test(flavor = "multi_thread")]
2231 async fn test_execute_with_function_sketch() {
2232 let ast = r#"fn box(h, l, w) {
2233 myBox = startSketchOn(XY)
2234 |> startProfile(at = [0,0])
2235 |> line(end = [0, l])
2236 |> line(end = [w, 0])
2237 |> line(end = [0, -l])
2238 |> close()
2239 |> extrude(length = h)
2240
2241 return myBox
2242}
2243
2244fnBox = box(h = 3, l = 6, w = 10)"#;
2245
2246 parse_execute(ast).await.unwrap();
2247 }
2248
2249 #[tokio::test(flavor = "multi_thread")]
2250 async fn test_get_member_of_object_with_function_period() {
2251 let ast = r#"fn box(@obj) {
2252 myBox = startSketchOn(XY)
2253 |> startProfile(at = obj.start)
2254 |> line(end = [0, obj.l])
2255 |> line(end = [obj.w, 0])
2256 |> line(end = [0, -obj.l])
2257 |> close()
2258 |> extrude(length = obj.h)
2259
2260 return myBox
2261}
2262
2263thisBox = box({start = [0,0], l = 6, w = 10, h = 3})
2264"#;
2265 parse_execute(ast).await.unwrap();
2266 }
2267
2268 #[tokio::test(flavor = "multi_thread")]
2269 #[ignore] async fn test_object_member_starting_pipeline() {
2271 let ast = r#"
2272fn test2() {
2273 return {
2274 thing: startSketchOn(XY)
2275 |> startProfile(at = [0, 0])
2276 |> line(end = [0, 1])
2277 |> line(end = [1, 0])
2278 |> line(end = [0, -1])
2279 |> close()
2280 }
2281}
2282
2283x2 = test2()
2284
2285x2.thing
2286 |> extrude(length = 10)
2287"#;
2288 parse_execute(ast).await.unwrap();
2289 }
2290
2291 #[tokio::test(flavor = "multi_thread")]
2292 #[ignore] async fn test_execute_with_function_sketch_loop_objects() {
2294 let ast = r#"fn box(obj) {
2295let myBox = startSketchOn(XY)
2296 |> startProfile(at = obj.start)
2297 |> line(end = [0, obj.l])
2298 |> line(end = [obj.w, 0])
2299 |> line(end = [0, -obj.l])
2300 |> close()
2301 |> extrude(length = obj.h)
2302
2303 return myBox
2304}
2305
2306for var in [{start: [0,0], l: 6, w: 10, h: 3}, {start: [-10,-10], l: 3, w: 5, h: 1.5}] {
2307 thisBox = box(var)
2308}"#;
2309
2310 parse_execute(ast).await.unwrap();
2311 }
2312
2313 #[tokio::test(flavor = "multi_thread")]
2314 #[ignore] async fn test_execute_with_function_sketch_loop_array() {
2316 let ast = r#"fn box(h, l, w, start) {
2317 myBox = startSketchOn(XY)
2318 |> startProfile(at = [0,0])
2319 |> line(end = [0, l])
2320 |> line(end = [w, 0])
2321 |> line(end = [0, -l])
2322 |> close()
2323 |> extrude(length = h)
2324
2325 return myBox
2326}
2327
2328
2329for var in [[3, 6, 10, [0,0]], [1.5, 3, 5, [-10,-10]]] {
2330 const thisBox = box(var[0], var[1], var[2], var[3])
2331}"#;
2332
2333 parse_execute(ast).await.unwrap();
2334 }
2335
2336 #[tokio::test(flavor = "multi_thread")]
2337 async fn test_get_member_of_array_with_function() {
2338 let ast = r#"fn box(@arr) {
2339 myBox =startSketchOn(XY)
2340 |> startProfile(at = arr[0])
2341 |> line(end = [0, arr[1]])
2342 |> line(end = [arr[2], 0])
2343 |> line(end = [0, -arr[1]])
2344 |> close()
2345 |> extrude(length = arr[3])
2346
2347 return myBox
2348}
2349
2350thisBox = box([[0,0], 6, 10, 3])
2351
2352"#;
2353 parse_execute(ast).await.unwrap();
2354 }
2355
2356 #[tokio::test(flavor = "multi_thread")]
2357 async fn test_function_cannot_access_future_definitions() {
2358 let ast = r#"
2359fn returnX() {
2360 // x shouldn't be defined yet.
2361 return x
2362}
2363
2364x = 5
2365
2366answer = returnX()"#;
2367
2368 let result = parse_execute(ast).await;
2369 let err = result.unwrap_err();
2370 assert_eq!(err.message(), "`x` is not defined");
2371 }
2372
2373 #[tokio::test(flavor = "multi_thread")]
2374 async fn test_override_prelude() {
2375 let text = "PI = 3.0";
2376 let result = parse_execute(text).await.unwrap();
2377 let errs = result.exec_state.errors();
2378 assert!(errs.is_empty());
2379 }
2380
2381 #[tokio::test(flavor = "multi_thread")]
2382 async fn type_aliases() {
2383 let text = r#"@settings(experimentalFeatures = allow)
2384type MyTy = [number; 2]
2385fn foo(@x: MyTy) {
2386 return x[0]
2387}
2388
2389foo([0, 1])
2390
2391type Other = MyTy | Helix
2392"#;
2393 let result = parse_execute(text).await.unwrap();
2394 let errs = result.exec_state.errors();
2395 assert!(errs.is_empty());
2396 }
2397
2398 #[tokio::test(flavor = "multi_thread")]
2399 async fn test_cannot_shebang_in_fn() {
2400 let ast = r#"
2401fn foo() {
2402 #!hello
2403 return true
2404}
2405
2406foo
2407"#;
2408
2409 let result = parse_execute(ast).await;
2410 let err = result.unwrap_err();
2411 assert_eq!(
2412 err,
2413 KclError::new_syntax(KclErrorDetails::new(
2414 "Unexpected token: #".to_owned(),
2415 vec![SourceRange::new(14, 15, ModuleId::default())],
2416 )),
2417 );
2418 }
2419
2420 #[tokio::test(flavor = "multi_thread")]
2421 async fn test_pattern_transform_function_cannot_access_future_definitions() {
2422 let ast = r#"
2423fn transform(@replicaId) {
2424 // x shouldn't be defined yet.
2425 scale = x
2426 return {
2427 translate = [0, 0, replicaId * 10],
2428 scale = [scale, 1, 0],
2429 }
2430}
2431
2432fn layer() {
2433 return startSketchOn(XY)
2434 |> circle( center= [0, 0], radius= 1, tag = $tag1)
2435 |> extrude(length = 10)
2436}
2437
2438x = 5
2439
2440// The 10 layers are replicas of each other, with a transform applied to each.
2441shape = layer() |> patternTransform(instances = 10, transform = transform)
2442"#;
2443
2444 let result = parse_execute(ast).await;
2445 let err = result.unwrap_err();
2446 assert_eq!(err.message(), "`x` is not defined",);
2447 }
2448
2449 #[tokio::test(flavor = "multi_thread")]
2452 async fn test_math_execute_with_functions() {
2453 let ast = r#"myVar = 2 + min([100, -1 + legLen(hypotenuse = 5, leg = 3)])"#;
2454 let result = parse_execute(ast).await.unwrap();
2455 assert_eq!(
2456 5.0,
2457 mem_get_json(result.exec_state.stack(), result.mem_env, "myVar")
2458 .as_f64()
2459 .unwrap()
2460 );
2461 }
2462
2463 #[tokio::test(flavor = "multi_thread")]
2464 async fn test_math_execute() {
2465 let ast = r#"myVar = 1 + 2 * (3 - 4) / -5 + 6"#;
2466 let result = parse_execute(ast).await.unwrap();
2467 assert_eq!(
2468 7.4,
2469 mem_get_json(result.exec_state.stack(), result.mem_env, "myVar")
2470 .as_f64()
2471 .unwrap()
2472 );
2473 }
2474
2475 #[tokio::test(flavor = "multi_thread")]
2476 async fn test_math_execute_start_negative() {
2477 let ast = r#"myVar = -5 + 6"#;
2478 let result = parse_execute(ast).await.unwrap();
2479 assert_eq!(
2480 1.0,
2481 mem_get_json(result.exec_state.stack(), result.mem_env, "myVar")
2482 .as_f64()
2483 .unwrap()
2484 );
2485 }
2486
2487 #[tokio::test(flavor = "multi_thread")]
2488 async fn test_math_execute_with_pi() {
2489 let ast = r#"myVar = PI * 2"#;
2490 let result = parse_execute(ast).await.unwrap();
2491 assert_eq!(
2492 std::f64::consts::TAU,
2493 mem_get_json(result.exec_state.stack(), result.mem_env, "myVar")
2494 .as_f64()
2495 .unwrap()
2496 );
2497 }
2498
2499 #[tokio::test(flavor = "multi_thread")]
2500 async fn test_math_define_decimal_without_leading_zero() {
2501 let ast = r#"thing = .4 + 7"#;
2502 let result = parse_execute(ast).await.unwrap();
2503 assert_eq!(
2504 7.4,
2505 mem_get_json(result.exec_state.stack(), result.mem_env, "thing")
2506 .as_f64()
2507 .unwrap()
2508 );
2509 }
2510
2511 #[tokio::test(flavor = "multi_thread")]
2512 async fn pass_std_to_std() {
2513 let ast = r#"sketch001 = startSketchOn(XY)
2514profile001 = circle(sketch001, center = [0, 0], radius = 2)
2515extrude001 = extrude(profile001, length = 5)
2516extrudes = patternLinear3d(
2517 extrude001,
2518 instances = 3,
2519 distance = 5,
2520 axis = [1, 1, 0],
2521)
2522clone001 = map(extrudes, f = clone)
2523"#;
2524 parse_execute(ast).await.unwrap();
2525 }
2526
2527 #[tokio::test(flavor = "multi_thread")]
2528 async fn test_array_reduce_nested_array() {
2529 let code = r#"
2530fn id(@el, accum) { return accum }
2531
2532answer = reduce([], initial=[[[0,0]]], f=id)
2533"#;
2534 let result = parse_execute(code).await.unwrap();
2535 assert_eq!(
2536 mem_get_json(result.exec_state.stack(), result.mem_env, "answer"),
2537 KclValue::HomArray {
2538 value: vec![KclValue::HomArray {
2539 value: vec![KclValue::HomArray {
2540 value: vec![
2541 KclValue::Number {
2542 value: 0.0,
2543 ty: NumericType::default(),
2544 meta: vec![SourceRange::new(69, 70, Default::default()).into()],
2545 },
2546 KclValue::Number {
2547 value: 0.0,
2548 ty: NumericType::default(),
2549 meta: vec![SourceRange::new(71, 72, Default::default()).into()],
2550 }
2551 ],
2552 ty: RuntimeType::any(),
2553 }],
2554 ty: RuntimeType::any(),
2555 }],
2556 ty: RuntimeType::any(),
2557 }
2558 );
2559 }
2560
2561 #[tokio::test(flavor = "multi_thread")]
2562 async fn test_zero_param_fn() {
2563 let ast = r#"sigmaAllow = 35000 // psi
2564leg1 = 5 // inches
2565leg2 = 8 // inches
2566fn thickness() { return 0.56 }
2567
2568bracket = startSketchOn(XY)
2569 |> startProfile(at = [0,0])
2570 |> line(end = [0, leg1])
2571 |> line(end = [leg2, 0])
2572 |> line(end = [0, -thickness()])
2573 |> line(end = [-leg2 + thickness(), 0])
2574"#;
2575 parse_execute(ast).await.unwrap();
2576 }
2577
2578 #[tokio::test(flavor = "multi_thread")]
2579 async fn test_unary_operator_not_succeeds() {
2580 let ast = r#"
2581fn returnTrue() { return !false }
2582t = true
2583f = false
2584notTrue = !t
2585notFalse = !f
2586c = !!true
2587d = !returnTrue()
2588
2589assertIs(!false, error = "expected to pass")
2590
2591fn check(x) {
2592 assertIs(!x, error = "expected argument to be false")
2593 return true
2594}
2595check(x = false)
2596"#;
2597 let result = parse_execute(ast).await.unwrap();
2598 assert_eq!(
2599 false,
2600 mem_get_json(result.exec_state.stack(), result.mem_env, "notTrue")
2601 .as_bool()
2602 .unwrap()
2603 );
2604 assert_eq!(
2605 true,
2606 mem_get_json(result.exec_state.stack(), result.mem_env, "notFalse")
2607 .as_bool()
2608 .unwrap()
2609 );
2610 assert_eq!(
2611 true,
2612 mem_get_json(result.exec_state.stack(), result.mem_env, "c")
2613 .as_bool()
2614 .unwrap()
2615 );
2616 assert_eq!(
2617 false,
2618 mem_get_json(result.exec_state.stack(), result.mem_env, "d")
2619 .as_bool()
2620 .unwrap()
2621 );
2622 }
2623
2624 #[tokio::test(flavor = "multi_thread")]
2625 async fn test_unary_operator_not_on_non_bool_fails() {
2626 let code1 = r#"
2627// Yup, this is null.
2628myNull = 0 / 0
2629notNull = !myNull
2630"#;
2631 assert_eq!(
2632 parse_execute(code1).await.unwrap_err().message(),
2633 "Cannot apply unary operator ! to non-boolean value: a number",
2634 );
2635
2636 let code2 = "notZero = !0";
2637 assert_eq!(
2638 parse_execute(code2).await.unwrap_err().message(),
2639 "Cannot apply unary operator ! to non-boolean value: a number",
2640 );
2641
2642 let code3 = r#"
2643notEmptyString = !""
2644"#;
2645 assert_eq!(
2646 parse_execute(code3).await.unwrap_err().message(),
2647 "Cannot apply unary operator ! to non-boolean value: a string",
2648 );
2649
2650 let code4 = r#"
2651obj = { a = 1 }
2652notMember = !obj.a
2653"#;
2654 assert_eq!(
2655 parse_execute(code4).await.unwrap_err().message(),
2656 "Cannot apply unary operator ! to non-boolean value: a number",
2657 );
2658
2659 let code5 = "
2660a = []
2661notArray = !a";
2662 assert_eq!(
2663 parse_execute(code5).await.unwrap_err().message(),
2664 "Cannot apply unary operator ! to non-boolean value: an empty array",
2665 );
2666
2667 let code6 = "
2668x = {}
2669notObject = !x";
2670 assert_eq!(
2671 parse_execute(code6).await.unwrap_err().message(),
2672 "Cannot apply unary operator ! to non-boolean value: an object",
2673 );
2674
2675 let code7 = "
2676fn x() { return 1 }
2677notFunction = !x";
2678 let fn_err = parse_execute(code7).await.unwrap_err();
2679 assert!(
2682 fn_err
2683 .message()
2684 .starts_with("Cannot apply unary operator ! to non-boolean value: "),
2685 "Actual error: {fn_err:?}"
2686 );
2687
2688 let code8 = "
2689myTagDeclarator = $myTag
2690notTagDeclarator = !myTagDeclarator";
2691 let tag_declarator_err = parse_execute(code8).await.unwrap_err();
2692 assert!(
2695 tag_declarator_err
2696 .message()
2697 .starts_with("Cannot apply unary operator ! to non-boolean value: a tag declarator"),
2698 "Actual error: {tag_declarator_err:?}"
2699 );
2700
2701 let code9 = "
2702myTagDeclarator = $myTag
2703notTagIdentifier = !myTag";
2704 let tag_identifier_err = parse_execute(code9).await.unwrap_err();
2705 assert!(
2708 tag_identifier_err
2709 .message()
2710 .starts_with("Cannot apply unary operator ! to non-boolean value: a tag identifier"),
2711 "Actual error: {tag_identifier_err:?}"
2712 );
2713
2714 let code10 = "notPipe = !(1 |> 2)";
2715 assert_eq!(
2716 parse_execute(code10).await.unwrap_err(),
2719 KclError::new_syntax(KclErrorDetails::new(
2720 "Unexpected token: !".to_owned(),
2721 vec![SourceRange::new(10, 11, ModuleId::default())],
2722 ))
2723 );
2724
2725 let code11 = "
2726fn identity(x) { return x }
2727notPipeSub = 1 |> identity(!%))";
2728 assert_eq!(
2729 parse_execute(code11).await.unwrap_err(),
2732 KclError::new_syntax(KclErrorDetails::new(
2733 "There was an unexpected `!`. Try removing it.".to_owned(),
2734 vec![SourceRange::new(56, 57, ModuleId::default())],
2735 ))
2736 );
2737
2738 }
2742
2743 #[tokio::test(flavor = "multi_thread")]
2744 async fn test_start_sketch_on_invalid_kwargs() {
2745 let current_dir = std::env::current_dir().unwrap();
2746 let mut path = current_dir.join("tests/inputs/startSketchOn_0.kcl");
2747 let mut code = std::fs::read_to_string(&path).unwrap();
2748 assert_eq!(
2749 parse_execute(&code).await.unwrap_err().message(),
2750 "You cannot give both `face` and `normalToFace` params, you have to choose one or the other.".to_owned(),
2751 );
2752
2753 path = current_dir.join("tests/inputs/startSketchOn_1.kcl");
2754 code = std::fs::read_to_string(&path).unwrap();
2755
2756 assert_eq!(
2757 parse_execute(&code).await.unwrap_err().message(),
2758 "`alignAxis` is required if `normalToFace` is specified.".to_owned(),
2759 );
2760
2761 path = current_dir.join("tests/inputs/startSketchOn_2.kcl");
2762 code = std::fs::read_to_string(&path).unwrap();
2763
2764 assert_eq!(
2765 parse_execute(&code).await.unwrap_err().message(),
2766 "`normalToFace` is required if `alignAxis` is specified.".to_owned(),
2767 );
2768
2769 path = current_dir.join("tests/inputs/startSketchOn_3.kcl");
2770 code = std::fs::read_to_string(&path).unwrap();
2771
2772 assert_eq!(
2773 parse_execute(&code).await.unwrap_err().message(),
2774 "`normalToFace` is required if `alignAxis` is specified.".to_owned(),
2775 );
2776
2777 path = current_dir.join("tests/inputs/startSketchOn_4.kcl");
2778 code = std::fs::read_to_string(&path).unwrap();
2779
2780 assert_eq!(
2781 parse_execute(&code).await.unwrap_err().message(),
2782 "`normalToFace` is required if `normalOffset` is specified.".to_owned(),
2783 );
2784 }
2785
2786 #[tokio::test(flavor = "multi_thread")]
2787 async fn test_math_negative_variable_in_binary_expression() {
2788 let ast = r#"sigmaAllow = 35000 // psi
2789width = 1 // inch
2790
2791p = 150 // lbs
2792distance = 6 // inches
2793FOS = 2
2794
2795leg1 = 5 // inches
2796leg2 = 8 // inches
2797
2798thickness_squared = distance * p * FOS * 6 / sigmaAllow
2799thickness = 0.56 // inches. App does not support square root function yet
2800
2801bracket = startSketchOn(XY)
2802 |> startProfile(at = [0,0])
2803 |> line(end = [0, leg1])
2804 |> line(end = [leg2, 0])
2805 |> line(end = [0, -thickness])
2806 |> line(end = [-leg2 + thickness, 0])
2807"#;
2808 parse_execute(ast).await.unwrap();
2809 }
2810
2811 #[tokio::test(flavor = "multi_thread")]
2812 async fn test_execute_function_no_return() {
2813 let ast = r#"fn test(@origin) {
2814 origin
2815}
2816
2817test([0, 0])
2818"#;
2819 let result = parse_execute(ast).await;
2820 assert!(result.is_err());
2821 assert!(result.unwrap_err().to_string().contains("undefined"));
2822 }
2823
2824 #[tokio::test(flavor = "multi_thread")]
2825 async fn test_max_stack_size_exceeded_error() {
2826 let ast = r#"
2827fn forever(@n) {
2828 return 1 + forever(n)
2829}
2830
2831forever(1)
2832"#;
2833 let result = parse_execute(ast).await;
2834 let err = result.unwrap_err();
2835 assert!(err.to_string().contains("stack size exceeded"), "actual: {:?}", err);
2836 }
2837
2838 #[tokio::test(flavor = "multi_thread")]
2839 async fn test_math_doubly_nested_parens() {
2840 let ast = r#"sigmaAllow = 35000 // psi
2841width = 4 // inch
2842p = 150 // Force on shelf - lbs
2843distance = 6 // inches
2844FOS = 2
2845leg1 = 5 // inches
2846leg2 = 8 // inches
2847thickness_squared = (distance * p * FOS * 6 / (sigmaAllow - width))
2848thickness = 0.32 // inches. App does not support square root function yet
2849bracket = startSketchOn(XY)
2850 |> startProfile(at = [0,0])
2851 |> line(end = [0, leg1])
2852 |> line(end = [leg2, 0])
2853 |> line(end = [0, -thickness])
2854 |> line(end = [-1 * leg2 + thickness, 0])
2855 |> line(end = [0, -1 * leg1 + thickness])
2856 |> close()
2857 |> extrude(length = width)
2858"#;
2859 parse_execute(ast).await.unwrap();
2860 }
2861
2862 #[tokio::test(flavor = "multi_thread")]
2863 async fn test_math_nested_parens_one_less() {
2864 let ast = r#" sigmaAllow = 35000 // psi
2865width = 4 // inch
2866p = 150 // Force on shelf - lbs
2867distance = 6 // inches
2868FOS = 2
2869leg1 = 5 // inches
2870leg2 = 8 // inches
2871thickness_squared = distance * p * FOS * 6 / (sigmaAllow - width)
2872thickness = 0.32 // inches. App does not support square root function yet
2873bracket = startSketchOn(XY)
2874 |> startProfile(at = [0,0])
2875 |> line(end = [0, leg1])
2876 |> line(end = [leg2, 0])
2877 |> line(end = [0, -thickness])
2878 |> line(end = [-1 * leg2 + thickness, 0])
2879 |> line(end = [0, -1 * leg1 + thickness])
2880 |> close()
2881 |> extrude(length = width)
2882"#;
2883 parse_execute(ast).await.unwrap();
2884 }
2885
2886 #[tokio::test(flavor = "multi_thread")]
2887 async fn test_fn_as_operand() {
2888 let ast = r#"fn f() { return 1 }
2889x = f()
2890y = x + 1
2891z = f() + 1
2892w = f() + f()
2893"#;
2894 parse_execute(ast).await.unwrap();
2895 }
2896
2897 #[tokio::test(flavor = "multi_thread")]
2898 async fn kcl_test_ids_stable_between_executions() {
2899 let code = r#"sketch001 = startSketchOn(XZ)
2900|> startProfile(at = [61.74, 206.13])
2901|> xLine(length = 305.11, tag = $seg01)
2902|> yLine(length = -291.85)
2903|> xLine(length = -segLen(seg01))
2904|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
2905|> close()
2906|> extrude(length = 40.14)
2907|> shell(
2908 thickness = 3.14,
2909 faces = [seg01]
2910)
2911"#;
2912
2913 let ctx = crate::test_server::new_context(true, None).await.unwrap();
2914 let old_program = crate::Program::parse_no_errs(code).unwrap();
2915
2916 if let Err(err) = ctx.run_with_caching(old_program).await {
2918 let report = err.into_miette_report_with_outputs(code).unwrap();
2919 let report = miette::Report::new(report);
2920 panic!("Error executing program: {report:?}");
2921 }
2922
2923 let id_generator = cache::read_old_ast().await.unwrap().main.exec_state.id_generator;
2925
2926 let code = r#"sketch001 = startSketchOn(XZ)
2927|> startProfile(at = [62.74, 206.13])
2928|> xLine(length = 305.11, tag = $seg01)
2929|> yLine(length = -291.85)
2930|> xLine(length = -segLen(seg01))
2931|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
2932|> close()
2933|> extrude(length = 40.14)
2934|> shell(
2935 faces = [seg01],
2936 thickness = 3.14,
2937)
2938"#;
2939
2940 let program = crate::Program::parse_no_errs(code).unwrap();
2942 ctx.run_with_caching(program).await.unwrap();
2944
2945 let new_id_generator = cache::read_old_ast().await.unwrap().main.exec_state.id_generator;
2946
2947 assert_eq!(id_generator, new_id_generator);
2948 }
2949
2950 #[tokio::test(flavor = "multi_thread")]
2951 async fn kcl_test_changing_a_setting_updates_the_cached_state() {
2952 let code = r#"sketch001 = startSketchOn(XZ)
2953|> startProfile(at = [61.74, 206.13])
2954|> xLine(length = 305.11, tag = $seg01)
2955|> yLine(length = -291.85)
2956|> xLine(length = -segLen(seg01))
2957|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
2958|> close()
2959|> extrude(length = 40.14)
2960|> shell(
2961 thickness = 3.14,
2962 faces = [seg01]
2963)
2964"#;
2965
2966 let mut ctx = crate::test_server::new_context(true, None).await.unwrap();
2967 let old_program = crate::Program::parse_no_errs(code).unwrap();
2968
2969 ctx.run_with_caching(old_program.clone()).await.unwrap();
2971
2972 let settings_state = cache::read_old_ast().await.unwrap().settings;
2973
2974 assert_eq!(settings_state, ctx.settings);
2976
2977 ctx.settings.highlight_edges = !ctx.settings.highlight_edges;
2979
2980 ctx.run_with_caching(old_program.clone()).await.unwrap();
2982
2983 let settings_state = cache::read_old_ast().await.unwrap().settings;
2984
2985 assert_eq!(settings_state, ctx.settings);
2987
2988 ctx.settings.highlight_edges = !ctx.settings.highlight_edges;
2990
2991 ctx.run_with_caching(old_program).await.unwrap();
2993
2994 let settings_state = cache::read_old_ast().await.unwrap().settings;
2995
2996 assert_eq!(settings_state, ctx.settings);
2998
2999 ctx.close().await;
3000 }
3001
3002 #[tokio::test(flavor = "multi_thread")]
3003 async fn mock_after_not_mock() {
3004 let ctx = ExecutorContext::new_with_default_client().await.unwrap();
3005 let program = crate::Program::parse_no_errs("x = 2").unwrap();
3006 let result = ctx.run_with_caching(program).await.unwrap();
3007 assert_eq!(result.variables.get("x").unwrap().as_f64().unwrap(), 2.0);
3008
3009 let ctx2 = ExecutorContext::new_mock(None).await;
3010 let program2 = crate::Program::parse_no_errs("z = x + 1").unwrap();
3011 let result = ctx2.run_mock(&program2, &MockConfig::default()).await.unwrap();
3012 assert_eq!(result.variables.get("z").unwrap().as_f64().unwrap(), 3.0);
3013
3014 ctx.close().await;
3015 ctx2.close().await;
3016 }
3017
3018 #[tokio::test(flavor = "multi_thread")]
3019 async fn mock_then_add_extrude_then_mock_again() {
3020 let code = "@settings(experimentalFeatures = allow)
3021
3022s = sketch(on = XY) {
3023 line1 = line(start = [0.05, 0.05], end = [3.88, 0.81])
3024 line2 = line(start = [3.88, 0.81], end = [0.92, 4.67])
3025 coincident([line1.end, line2.start])
3026 line3 = line(start = [0.92, 4.67], end = [0.05, 0.05])
3027 coincident([line2.end, line3.start])
3028 coincident([line1.start, line3.end])
3029}
3030 ";
3031 let ctx = ExecutorContext::new_mock(None).await;
3032 let program = crate::Program::parse_no_errs(code).unwrap();
3033 let result = ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
3034 assert!(result.variables.contains_key("s"), "actual: {:?}", &result.variables);
3035
3036 let code2 = code.to_owned()
3037 + "
3038region001 = region(point = [1mm, 1mm], sketch = s)
3039extrude001 = extrude(region001, length = 1)
3040 ";
3041 let program2 = crate::Program::parse_no_errs(&code2).unwrap();
3042 let result = ctx.run_mock(&program2, &MockConfig::default()).await.unwrap();
3043 assert!(
3044 result.variables.contains_key("region001"),
3045 "actual: {:?}",
3046 &result.variables
3047 );
3048
3049 ctx.close().await;
3050 }
3051
3052 #[cfg(feature = "artifact-graph")]
3053 #[tokio::test(flavor = "multi_thread")]
3054 async fn mock_has_stable_ids() {
3055 let ctx = ExecutorContext::new_mock(None).await;
3056 let mock_config = MockConfig {
3057 use_prev_memory: false,
3058 ..Default::default()
3059 };
3060 let code = "sk = startSketchOn(XY)
3061 |> startProfile(at = [0, 0])";
3062 let program = crate::Program::parse_no_errs(code).unwrap();
3063 let result = ctx.run_mock(&program, &mock_config).await.unwrap();
3064 let ids = result.artifact_graph.iter().map(|(k, _)| *k).collect::<Vec<_>>();
3065 assert!(!ids.is_empty(), "IDs should not be empty");
3066
3067 let ctx2 = ExecutorContext::new_mock(None).await;
3068 let program2 = crate::Program::parse_no_errs(code).unwrap();
3069 let result = ctx2.run_mock(&program2, &mock_config).await.unwrap();
3070 let ids2 = result.artifact_graph.iter().map(|(k, _)| *k).collect::<Vec<_>>();
3071
3072 assert_eq!(ids, ids2, "Generated IDs should match");
3073 ctx.close().await;
3074 ctx2.close().await;
3075 }
3076
3077 #[cfg(feature = "artifact-graph")]
3078 #[tokio::test(flavor = "multi_thread")]
3079 async fn sim_sketch_mode_real_mock_real() {
3080 let ctx = ExecutorContext::new_with_default_client().await.unwrap();
3081 let code = r#"sketch001 = startSketchOn(XY)
3082profile001 = startProfile(sketch001, at = [0, 0])
3083 |> line(end = [10, 0])
3084 |> line(end = [0, 10])
3085 |> line(end = [-10, 0])
3086 |> line(end = [0, -10])
3087 |> close()
3088"#;
3089 let program = crate::Program::parse_no_errs(code).unwrap();
3090 let result = ctx.run_with_caching(program).await.unwrap();
3091 assert_eq!(result.operations.len(), 1);
3092
3093 let mock_ctx = ExecutorContext::new_mock(None).await;
3094 let mock_program = crate::Program::parse_no_errs(code).unwrap();
3095 let mock_result = mock_ctx.run_mock(&mock_program, &MockConfig::default()).await.unwrap();
3096 assert_eq!(mock_result.operations.len(), 1);
3097
3098 let code2 = code.to_owned()
3099 + r#"
3100extrude001 = extrude(profile001, length = 10)
3101"#;
3102 let program2 = crate::Program::parse_no_errs(&code2).unwrap();
3103 let result = ctx.run_with_caching(program2).await.unwrap();
3104 assert_eq!(result.operations.len(), 2);
3105
3106 ctx.close().await;
3107 mock_ctx.close().await;
3108 }
3109
3110 #[tokio::test(flavor = "multi_thread")]
3111 async fn read_tag_version() {
3112 let ast = r#"fn bar(@t) {
3113 return startSketchOn(XY)
3114 |> startProfile(at = [0,0])
3115 |> angledLine(
3116 angle = -60,
3117 length = segLen(t),
3118 )
3119 |> line(end = [0, 0])
3120 |> close()
3121}
3122
3123sketch = startSketchOn(XY)
3124 |> startProfile(at = [0,0])
3125 |> line(end = [0, 10])
3126 |> line(end = [10, 0], tag = $tag0)
3127 |> line(endAbsolute = [0, 0])
3128
3129fn foo() {
3130 // tag0 tags an edge
3131 return bar(tag0)
3132}
3133
3134solid = sketch |> extrude(length = 10)
3135// tag0 tags a face
3136sketch2 = startSketchOn(solid, face = tag0)
3137 |> startProfile(at = [0,0])
3138 |> line(end = [0, 1])
3139 |> line(end = [1, 0])
3140 |> line(end = [0, 0])
3141
3142foo() |> extrude(length = 1)
3143"#;
3144 parse_execute(ast).await.unwrap();
3145 }
3146
3147 #[tokio::test(flavor = "multi_thread")]
3148 async fn experimental() {
3149 let code = r#"
3150startSketchOn(XY)
3151 |> startProfile(at = [0, 0], tag = $start)
3152 |> elliptic(center = [0, 0], angleStart = segAng(start), angleEnd = 160deg, majorRadius = 2, minorRadius = 3)
3153"#;
3154 let result = parse_execute(code).await.unwrap();
3155 let errors = result.exec_state.errors();
3156 assert_eq!(errors.len(), 1);
3157 assert_eq!(errors[0].severity, Severity::Error);
3158 let msg = &errors[0].message;
3159 assert!(msg.contains("experimental"), "found {msg}");
3160
3161 let code = r#"@settings(experimentalFeatures = allow)
3162startSketchOn(XY)
3163 |> startProfile(at = [0, 0], tag = $start)
3164 |> elliptic(center = [0, 0], angleStart = segAng(start), angleEnd = 160deg, majorRadius = 2, minorRadius = 3)
3165"#;
3166 let result = parse_execute(code).await.unwrap();
3167 let errors = result.exec_state.errors();
3168 assert!(errors.is_empty());
3169
3170 let code = r#"@settings(experimentalFeatures = warn)
3171startSketchOn(XY)
3172 |> startProfile(at = [0, 0], tag = $start)
3173 |> elliptic(center = [0, 0], angleStart = segAng(start), angleEnd = 160deg, majorRadius = 2, minorRadius = 3)
3174"#;
3175 let result = parse_execute(code).await.unwrap();
3176 let errors = result.exec_state.errors();
3177 assert_eq!(errors.len(), 1);
3178 assert_eq!(errors[0].severity, Severity::Warning);
3179 let msg = &errors[0].message;
3180 assert!(msg.contains("experimental"), "found {msg}");
3181
3182 let code = r#"@settings(experimentalFeatures = deny)
3183startSketchOn(XY)
3184 |> startProfile(at = [0, 0], tag = $start)
3185 |> elliptic(center = [0, 0], angleStart = segAng(start), angleEnd = 160deg, majorRadius = 2, minorRadius = 3)
3186"#;
3187 let result = parse_execute(code).await.unwrap();
3188 let errors = result.exec_state.errors();
3189 assert_eq!(errors.len(), 1);
3190 assert_eq!(errors[0].severity, Severity::Error);
3191 let msg = &errors[0].message;
3192 assert!(msg.contains("experimental"), "found {msg}");
3193
3194 let code = r#"@settings(experimentalFeatures = foo)
3195startSketchOn(XY)
3196 |> startProfile(at = [0, 0], tag = $start)
3197 |> elliptic(center = [0, 0], angleStart = segAng(start), angleEnd = 160deg, majorRadius = 2, minorRadius = 3)
3198"#;
3199 parse_execute(code).await.unwrap_err();
3200 }
3201
3202 #[tokio::test(flavor = "multi_thread")]
3203 async fn experimental_parameter() {
3204 let code = r#"
3205fn inc(@x, @(experimental = true) amount? = 1) {
3206 return x + amount
3207}
3208
3209answer = inc(5, amount = 2)
3210"#;
3211 let result = parse_execute(code).await.unwrap();
3212 let errors = result.exec_state.errors();
3213 assert_eq!(errors.len(), 1);
3214 assert_eq!(errors[0].severity, Severity::Error);
3215 let msg = &errors[0].message;
3216 assert!(msg.contains("experimental"), "found {msg}");
3217
3218 let code = r#"
3220fn inc(@x, @(experimental = true) amount? = 1) {
3221 return x + amount
3222}
3223
3224answer = inc(5)
3225"#;
3226 let result = parse_execute(code).await.unwrap();
3227 let errors = result.exec_state.errors();
3228 assert_eq!(errors.len(), 0);
3229 }
3230
3231 #[tokio::test(flavor = "multi_thread")]
3235 async fn test_tangent_line_arc_executes_with_mock_engine() {
3236 let code = std::fs::read_to_string("tests/tangent_line_arc/input.kcl").unwrap();
3237 parse_execute(&code).await.unwrap();
3238 }
3239
3240 #[tokio::test(flavor = "multi_thread")]
3241 async fn test_tangent_arc_arc_math_only_executes_with_mock_engine() {
3242 let code = std::fs::read_to_string("tests/tangent_arc_arc_math_only/input.kcl").unwrap();
3243 parse_execute(&code).await.unwrap();
3244 }
3245
3246 #[tokio::test(flavor = "multi_thread")]
3247 async fn test_tangent_line_circle_executes_with_mock_engine() {
3248 let code = std::fs::read_to_string("tests/tangent_line_circle/input.kcl").unwrap();
3249 parse_execute(&code).await.unwrap();
3250 }
3251
3252 #[tokio::test(flavor = "multi_thread")]
3253 async fn test_tangent_circle_circle_native_executes_with_mock_engine() {
3254 let code = std::fs::read_to_string("tests/tangent_circle_circle_native/input.kcl").unwrap();
3255 parse_execute(&code).await.unwrap();
3256 }
3257
3258 }