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 test_execute_with_pipe_substitutions_unary() {
2070 let ast = r#"myVar = 3
2071part001 = startSketchOn(XY)
2072 |> startProfile(at = [0, 0])
2073 |> line(end = [3, 4], tag = $seg01)
2074 |> line(end = [
2075 min([segLen(seg01), myVar]),
2076 -legLen(hypotenuse = segLen(seg01), leg = myVar)
2077])
2078"#;
2079
2080 parse_execute(ast).await.unwrap();
2081 }
2082
2083 #[tokio::test(flavor = "multi_thread")]
2084 async fn test_execute_with_pipe_substitutions() {
2085 let ast = r#"myVar = 3
2086part001 = startSketchOn(XY)
2087 |> startProfile(at = [0, 0])
2088 |> line(end = [3, 4], tag = $seg01)
2089 |> line(end = [
2090 min([segLen(seg01), myVar]),
2091 legLen(hypotenuse = segLen(seg01), leg = myVar)
2092])
2093"#;
2094
2095 parse_execute(ast).await.unwrap();
2096 }
2097
2098 #[tokio::test(flavor = "multi_thread")]
2099 async fn test_execute_with_inline_comment() {
2100 let ast = r#"baseThick = 1
2101armAngle = 60
2102
2103baseThickHalf = baseThick / 2
2104halfArmAngle = armAngle / 2
2105
2106arrExpShouldNotBeIncluded = [1, 2, 3]
2107objExpShouldNotBeIncluded = { a = 1, b = 2, c = 3 }
2108
2109part001 = startSketchOn(XY)
2110 |> startProfile(at = [0, 0])
2111 |> yLine(endAbsolute = 1)
2112 |> xLine(length = 3.84) // selection-range-7ish-before-this
2113
2114variableBelowShouldNotBeIncluded = 3
2115"#;
2116
2117 parse_execute(ast).await.unwrap();
2118 }
2119
2120 #[tokio::test(flavor = "multi_thread")]
2121 async fn test_execute_with_function_literal_in_pipe() {
2122 let ast = r#"w = 20
2123l = 8
2124h = 10
2125
2126fn thing() {
2127 return -8
2128}
2129
2130firstExtrude = startSketchOn(XY)
2131 |> startProfile(at = [0,0])
2132 |> line(end = [0, l])
2133 |> line(end = [w, 0])
2134 |> line(end = [0, thing()])
2135 |> close()
2136 |> extrude(length = h)"#;
2137
2138 parse_execute(ast).await.unwrap();
2139 }
2140
2141 #[tokio::test(flavor = "multi_thread")]
2142 async fn test_execute_with_function_unary_in_pipe() {
2143 let ast = r#"w = 20
2144l = 8
2145h = 10
2146
2147fn thing(@x) {
2148 return -x
2149}
2150
2151firstExtrude = startSketchOn(XY)
2152 |> startProfile(at = [0,0])
2153 |> line(end = [0, l])
2154 |> line(end = [w, 0])
2155 |> line(end = [0, thing(8)])
2156 |> close()
2157 |> extrude(length = h)"#;
2158
2159 parse_execute(ast).await.unwrap();
2160 }
2161
2162 #[tokio::test(flavor = "multi_thread")]
2163 async fn test_execute_with_function_array_in_pipe() {
2164 let ast = r#"w = 20
2165l = 8
2166h = 10
2167
2168fn thing(@x) {
2169 return [0, -x]
2170}
2171
2172firstExtrude = startSketchOn(XY)
2173 |> startProfile(at = [0,0])
2174 |> line(end = [0, l])
2175 |> line(end = [w, 0])
2176 |> line(end = thing(8))
2177 |> close()
2178 |> extrude(length = h)"#;
2179
2180 parse_execute(ast).await.unwrap();
2181 }
2182
2183 #[tokio::test(flavor = "multi_thread")]
2184 async fn test_execute_with_function_call_in_pipe() {
2185 let ast = r#"w = 20
2186l = 8
2187h = 10
2188
2189fn other_thing(@y) {
2190 return -y
2191}
2192
2193fn thing(@x) {
2194 return other_thing(x)
2195}
2196
2197firstExtrude = startSketchOn(XY)
2198 |> startProfile(at = [0,0])
2199 |> line(end = [0, l])
2200 |> line(end = [w, 0])
2201 |> line(end = [0, thing(8)])
2202 |> close()
2203 |> extrude(length = h)"#;
2204
2205 parse_execute(ast).await.unwrap();
2206 }
2207
2208 #[tokio::test(flavor = "multi_thread")]
2209 async fn test_execute_with_function_sketch() {
2210 let ast = r#"fn box(h, l, w) {
2211 myBox = startSketchOn(XY)
2212 |> startProfile(at = [0,0])
2213 |> line(end = [0, l])
2214 |> line(end = [w, 0])
2215 |> line(end = [0, -l])
2216 |> close()
2217 |> extrude(length = h)
2218
2219 return myBox
2220}
2221
2222fnBox = box(h = 3, l = 6, w = 10)"#;
2223
2224 parse_execute(ast).await.unwrap();
2225 }
2226
2227 #[tokio::test(flavor = "multi_thread")]
2228 async fn test_get_member_of_object_with_function_period() {
2229 let ast = r#"fn box(@obj) {
2230 myBox = startSketchOn(XY)
2231 |> startProfile(at = obj.start)
2232 |> line(end = [0, obj.l])
2233 |> line(end = [obj.w, 0])
2234 |> line(end = [0, -obj.l])
2235 |> close()
2236 |> extrude(length = obj.h)
2237
2238 return myBox
2239}
2240
2241thisBox = box({start = [0,0], l = 6, w = 10, h = 3})
2242"#;
2243 parse_execute(ast).await.unwrap();
2244 }
2245
2246 #[tokio::test(flavor = "multi_thread")]
2247 #[ignore] async fn test_object_member_starting_pipeline() {
2249 let ast = r#"
2250fn test2() {
2251 return {
2252 thing: startSketchOn(XY)
2253 |> startProfile(at = [0, 0])
2254 |> line(end = [0, 1])
2255 |> line(end = [1, 0])
2256 |> line(end = [0, -1])
2257 |> close()
2258 }
2259}
2260
2261x2 = test2()
2262
2263x2.thing
2264 |> extrude(length = 10)
2265"#;
2266 parse_execute(ast).await.unwrap();
2267 }
2268
2269 #[tokio::test(flavor = "multi_thread")]
2270 #[ignore] async fn test_execute_with_function_sketch_loop_objects() {
2272 let ast = r#"fn box(obj) {
2273let myBox = startSketchOn(XY)
2274 |> startProfile(at = obj.start)
2275 |> line(end = [0, obj.l])
2276 |> line(end = [obj.w, 0])
2277 |> line(end = [0, -obj.l])
2278 |> close()
2279 |> extrude(length = obj.h)
2280
2281 return myBox
2282}
2283
2284for var in [{start: [0,0], l: 6, w: 10, h: 3}, {start: [-10,-10], l: 3, w: 5, h: 1.5}] {
2285 thisBox = box(var)
2286}"#;
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_array() {
2294 let ast = r#"fn box(h, l, w, start) {
2295 myBox = startSketchOn(XY)
2296 |> startProfile(at = [0,0])
2297 |> line(end = [0, l])
2298 |> line(end = [w, 0])
2299 |> line(end = [0, -l])
2300 |> close()
2301 |> extrude(length = h)
2302
2303 return myBox
2304}
2305
2306
2307for var in [[3, 6, 10, [0,0]], [1.5, 3, 5, [-10,-10]]] {
2308 const thisBox = box(var[0], var[1], var[2], var[3])
2309}"#;
2310
2311 parse_execute(ast).await.unwrap();
2312 }
2313
2314 #[tokio::test(flavor = "multi_thread")]
2315 async fn test_get_member_of_array_with_function() {
2316 let ast = r#"fn box(@arr) {
2317 myBox =startSketchOn(XY)
2318 |> startProfile(at = arr[0])
2319 |> line(end = [0, arr[1]])
2320 |> line(end = [arr[2], 0])
2321 |> line(end = [0, -arr[1]])
2322 |> close()
2323 |> extrude(length = arr[3])
2324
2325 return myBox
2326}
2327
2328thisBox = box([[0,0], 6, 10, 3])
2329
2330"#;
2331 parse_execute(ast).await.unwrap();
2332 }
2333
2334 #[tokio::test(flavor = "multi_thread")]
2335 async fn test_function_cannot_access_future_definitions() {
2336 let ast = r#"
2337fn returnX() {
2338 // x shouldn't be defined yet.
2339 return x
2340}
2341
2342x = 5
2343
2344answer = returnX()"#;
2345
2346 let result = parse_execute(ast).await;
2347 let err = result.unwrap_err();
2348 assert_eq!(err.message(), "`x` is not defined");
2349 }
2350
2351 #[tokio::test(flavor = "multi_thread")]
2352 async fn test_override_prelude() {
2353 let text = "PI = 3.0";
2354 let result = parse_execute(text).await.unwrap();
2355 let errs = result.exec_state.errors();
2356 assert!(errs.is_empty());
2357 }
2358
2359 #[tokio::test(flavor = "multi_thread")]
2360 async fn type_aliases() {
2361 let text = r#"@settings(experimentalFeatures = allow)
2362type MyTy = [number; 2]
2363fn foo(@x: MyTy) {
2364 return x[0]
2365}
2366
2367foo([0, 1])
2368
2369type Other = MyTy | Helix
2370"#;
2371 let result = parse_execute(text).await.unwrap();
2372 let errs = result.exec_state.errors();
2373 assert!(errs.is_empty());
2374 }
2375
2376 #[tokio::test(flavor = "multi_thread")]
2377 async fn test_cannot_shebang_in_fn() {
2378 let ast = r#"
2379fn foo() {
2380 #!hello
2381 return true
2382}
2383
2384foo
2385"#;
2386
2387 let result = parse_execute(ast).await;
2388 let err = result.unwrap_err();
2389 assert_eq!(
2390 err,
2391 KclError::new_syntax(KclErrorDetails::new(
2392 "Unexpected token: #".to_owned(),
2393 vec![SourceRange::new(14, 15, ModuleId::default())],
2394 )),
2395 );
2396 }
2397
2398 #[tokio::test(flavor = "multi_thread")]
2399 async fn test_pattern_transform_function_cannot_access_future_definitions() {
2400 let ast = r#"
2401fn transform(@replicaId) {
2402 // x shouldn't be defined yet.
2403 scale = x
2404 return {
2405 translate = [0, 0, replicaId * 10],
2406 scale = [scale, 1, 0],
2407 }
2408}
2409
2410fn layer() {
2411 return startSketchOn(XY)
2412 |> circle( center= [0, 0], radius= 1, tag = $tag1)
2413 |> extrude(length = 10)
2414}
2415
2416x = 5
2417
2418// The 10 layers are replicas of each other, with a transform applied to each.
2419shape = layer() |> patternTransform(instances = 10, transform = transform)
2420"#;
2421
2422 let result = parse_execute(ast).await;
2423 let err = result.unwrap_err();
2424 assert_eq!(err.message(), "`x` is not defined",);
2425 }
2426
2427 #[tokio::test(flavor = "multi_thread")]
2430 async fn test_math_execute_with_functions() {
2431 let ast = r#"myVar = 2 + min([100, -1 + legLen(hypotenuse = 5, leg = 3)])"#;
2432 let result = parse_execute(ast).await.unwrap();
2433 assert_eq!(
2434 5.0,
2435 mem_get_json(result.exec_state.stack(), result.mem_env, "myVar")
2436 .as_f64()
2437 .unwrap()
2438 );
2439 }
2440
2441 #[tokio::test(flavor = "multi_thread")]
2442 async fn test_math_execute() {
2443 let ast = r#"myVar = 1 + 2 * (3 - 4) / -5 + 6"#;
2444 let result = parse_execute(ast).await.unwrap();
2445 assert_eq!(
2446 7.4,
2447 mem_get_json(result.exec_state.stack(), result.mem_env, "myVar")
2448 .as_f64()
2449 .unwrap()
2450 );
2451 }
2452
2453 #[tokio::test(flavor = "multi_thread")]
2454 async fn test_math_execute_start_negative() {
2455 let ast = r#"myVar = -5 + 6"#;
2456 let result = parse_execute(ast).await.unwrap();
2457 assert_eq!(
2458 1.0,
2459 mem_get_json(result.exec_state.stack(), result.mem_env, "myVar")
2460 .as_f64()
2461 .unwrap()
2462 );
2463 }
2464
2465 #[tokio::test(flavor = "multi_thread")]
2466 async fn test_math_execute_with_pi() {
2467 let ast = r#"myVar = PI * 2"#;
2468 let result = parse_execute(ast).await.unwrap();
2469 assert_eq!(
2470 std::f64::consts::TAU,
2471 mem_get_json(result.exec_state.stack(), result.mem_env, "myVar")
2472 .as_f64()
2473 .unwrap()
2474 );
2475 }
2476
2477 #[tokio::test(flavor = "multi_thread")]
2478 async fn test_math_define_decimal_without_leading_zero() {
2479 let ast = r#"thing = .4 + 7"#;
2480 let result = parse_execute(ast).await.unwrap();
2481 assert_eq!(
2482 7.4,
2483 mem_get_json(result.exec_state.stack(), result.mem_env, "thing")
2484 .as_f64()
2485 .unwrap()
2486 );
2487 }
2488
2489 #[tokio::test(flavor = "multi_thread")]
2490 async fn pass_std_to_std() {
2491 let ast = r#"sketch001 = startSketchOn(XY)
2492profile001 = circle(sketch001, center = [0, 0], radius = 2)
2493extrude001 = extrude(profile001, length = 5)
2494extrudes = patternLinear3d(
2495 extrude001,
2496 instances = 3,
2497 distance = 5,
2498 axis = [1, 1, 0],
2499)
2500clone001 = map(extrudes, f = clone)
2501"#;
2502 parse_execute(ast).await.unwrap();
2503 }
2504
2505 #[tokio::test(flavor = "multi_thread")]
2506 async fn test_array_reduce_nested_array() {
2507 let code = r#"
2508fn id(@el, accum) { return accum }
2509
2510answer = reduce([], initial=[[[0,0]]], f=id)
2511"#;
2512 let result = parse_execute(code).await.unwrap();
2513 assert_eq!(
2514 mem_get_json(result.exec_state.stack(), result.mem_env, "answer"),
2515 KclValue::HomArray {
2516 value: vec![KclValue::HomArray {
2517 value: vec![KclValue::HomArray {
2518 value: vec![
2519 KclValue::Number {
2520 value: 0.0,
2521 ty: NumericType::default(),
2522 meta: vec![SourceRange::new(69, 70, Default::default()).into()],
2523 },
2524 KclValue::Number {
2525 value: 0.0,
2526 ty: NumericType::default(),
2527 meta: vec![SourceRange::new(71, 72, Default::default()).into()],
2528 }
2529 ],
2530 ty: RuntimeType::any(),
2531 }],
2532 ty: RuntimeType::any(),
2533 }],
2534 ty: RuntimeType::any(),
2535 }
2536 );
2537 }
2538
2539 #[tokio::test(flavor = "multi_thread")]
2540 async fn test_zero_param_fn() {
2541 let ast = r#"sigmaAllow = 35000 // psi
2542leg1 = 5 // inches
2543leg2 = 8 // inches
2544fn thickness() { return 0.56 }
2545
2546bracket = startSketchOn(XY)
2547 |> startProfile(at = [0,0])
2548 |> line(end = [0, leg1])
2549 |> line(end = [leg2, 0])
2550 |> line(end = [0, -thickness()])
2551 |> line(end = [-leg2 + thickness(), 0])
2552"#;
2553 parse_execute(ast).await.unwrap();
2554 }
2555
2556 #[tokio::test(flavor = "multi_thread")]
2557 async fn test_unary_operator_not_succeeds() {
2558 let ast = r#"
2559fn returnTrue() { return !false }
2560t = true
2561f = false
2562notTrue = !t
2563notFalse = !f
2564c = !!true
2565d = !returnTrue()
2566
2567assertIs(!false, error = "expected to pass")
2568
2569fn check(x) {
2570 assertIs(!x, error = "expected argument to be false")
2571 return true
2572}
2573check(x = false)
2574"#;
2575 let result = parse_execute(ast).await.unwrap();
2576 assert_eq!(
2577 false,
2578 mem_get_json(result.exec_state.stack(), result.mem_env, "notTrue")
2579 .as_bool()
2580 .unwrap()
2581 );
2582 assert_eq!(
2583 true,
2584 mem_get_json(result.exec_state.stack(), result.mem_env, "notFalse")
2585 .as_bool()
2586 .unwrap()
2587 );
2588 assert_eq!(
2589 true,
2590 mem_get_json(result.exec_state.stack(), result.mem_env, "c")
2591 .as_bool()
2592 .unwrap()
2593 );
2594 assert_eq!(
2595 false,
2596 mem_get_json(result.exec_state.stack(), result.mem_env, "d")
2597 .as_bool()
2598 .unwrap()
2599 );
2600 }
2601
2602 #[tokio::test(flavor = "multi_thread")]
2603 async fn test_unary_operator_not_on_non_bool_fails() {
2604 let code1 = r#"
2605// Yup, this is null.
2606myNull = 0 / 0
2607notNull = !myNull
2608"#;
2609 assert_eq!(
2610 parse_execute(code1).await.unwrap_err().message(),
2611 "Cannot apply unary operator ! to non-boolean value: a number",
2612 );
2613
2614 let code2 = "notZero = !0";
2615 assert_eq!(
2616 parse_execute(code2).await.unwrap_err().message(),
2617 "Cannot apply unary operator ! to non-boolean value: a number",
2618 );
2619
2620 let code3 = r#"
2621notEmptyString = !""
2622"#;
2623 assert_eq!(
2624 parse_execute(code3).await.unwrap_err().message(),
2625 "Cannot apply unary operator ! to non-boolean value: a string",
2626 );
2627
2628 let code4 = r#"
2629obj = { a = 1 }
2630notMember = !obj.a
2631"#;
2632 assert_eq!(
2633 parse_execute(code4).await.unwrap_err().message(),
2634 "Cannot apply unary operator ! to non-boolean value: a number",
2635 );
2636
2637 let code5 = "
2638a = []
2639notArray = !a";
2640 assert_eq!(
2641 parse_execute(code5).await.unwrap_err().message(),
2642 "Cannot apply unary operator ! to non-boolean value: an empty array",
2643 );
2644
2645 let code6 = "
2646x = {}
2647notObject = !x";
2648 assert_eq!(
2649 parse_execute(code6).await.unwrap_err().message(),
2650 "Cannot apply unary operator ! to non-boolean value: an object",
2651 );
2652
2653 let code7 = "
2654fn x() { return 1 }
2655notFunction = !x";
2656 let fn_err = parse_execute(code7).await.unwrap_err();
2657 assert!(
2660 fn_err
2661 .message()
2662 .starts_with("Cannot apply unary operator ! to non-boolean value: "),
2663 "Actual error: {fn_err:?}"
2664 );
2665
2666 let code8 = "
2667myTagDeclarator = $myTag
2668notTagDeclarator = !myTagDeclarator";
2669 let tag_declarator_err = parse_execute(code8).await.unwrap_err();
2670 assert!(
2673 tag_declarator_err
2674 .message()
2675 .starts_with("Cannot apply unary operator ! to non-boolean value: a tag declarator"),
2676 "Actual error: {tag_declarator_err:?}"
2677 );
2678
2679 let code9 = "
2680myTagDeclarator = $myTag
2681notTagIdentifier = !myTag";
2682 let tag_identifier_err = parse_execute(code9).await.unwrap_err();
2683 assert!(
2686 tag_identifier_err
2687 .message()
2688 .starts_with("Cannot apply unary operator ! to non-boolean value: a tag identifier"),
2689 "Actual error: {tag_identifier_err:?}"
2690 );
2691
2692 let code10 = "notPipe = !(1 |> 2)";
2693 assert_eq!(
2694 parse_execute(code10).await.unwrap_err(),
2697 KclError::new_syntax(KclErrorDetails::new(
2698 "Unexpected token: !".to_owned(),
2699 vec![SourceRange::new(10, 11, ModuleId::default())],
2700 ))
2701 );
2702
2703 let code11 = "
2704fn identity(x) { return x }
2705notPipeSub = 1 |> identity(!%))";
2706 assert_eq!(
2707 parse_execute(code11).await.unwrap_err(),
2710 KclError::new_syntax(KclErrorDetails::new(
2711 "There was an unexpected `!`. Try removing it.".to_owned(),
2712 vec![SourceRange::new(56, 57, ModuleId::default())],
2713 ))
2714 );
2715
2716 }
2720
2721 #[tokio::test(flavor = "multi_thread")]
2722 async fn test_start_sketch_on_invalid_kwargs() {
2723 let current_dir = std::env::current_dir().unwrap();
2724 let mut path = current_dir.join("tests/inputs/startSketchOn_0.kcl");
2725 let mut code = std::fs::read_to_string(&path).unwrap();
2726 assert_eq!(
2727 parse_execute(&code).await.unwrap_err().message(),
2728 "You cannot give both `face` and `normalToFace` params, you have to choose one or the other.".to_owned(),
2729 );
2730
2731 path = current_dir.join("tests/inputs/startSketchOn_1.kcl");
2732 code = std::fs::read_to_string(&path).unwrap();
2733
2734 assert_eq!(
2735 parse_execute(&code).await.unwrap_err().message(),
2736 "`alignAxis` is required if `normalToFace` is specified.".to_owned(),
2737 );
2738
2739 path = current_dir.join("tests/inputs/startSketchOn_2.kcl");
2740 code = std::fs::read_to_string(&path).unwrap();
2741
2742 assert_eq!(
2743 parse_execute(&code).await.unwrap_err().message(),
2744 "`normalToFace` is required if `alignAxis` is specified.".to_owned(),
2745 );
2746
2747 path = current_dir.join("tests/inputs/startSketchOn_3.kcl");
2748 code = std::fs::read_to_string(&path).unwrap();
2749
2750 assert_eq!(
2751 parse_execute(&code).await.unwrap_err().message(),
2752 "`normalToFace` is required if `alignAxis` is specified.".to_owned(),
2753 );
2754
2755 path = current_dir.join("tests/inputs/startSketchOn_4.kcl");
2756 code = std::fs::read_to_string(&path).unwrap();
2757
2758 assert_eq!(
2759 parse_execute(&code).await.unwrap_err().message(),
2760 "`normalToFace` is required if `normalOffset` is specified.".to_owned(),
2761 );
2762 }
2763
2764 #[tokio::test(flavor = "multi_thread")]
2765 async fn test_math_negative_variable_in_binary_expression() {
2766 let ast = r#"sigmaAllow = 35000 // psi
2767width = 1 // inch
2768
2769p = 150 // lbs
2770distance = 6 // inches
2771FOS = 2
2772
2773leg1 = 5 // inches
2774leg2 = 8 // inches
2775
2776thickness_squared = distance * p * FOS * 6 / sigmaAllow
2777thickness = 0.56 // inches. App does not support square root function yet
2778
2779bracket = startSketchOn(XY)
2780 |> startProfile(at = [0,0])
2781 |> line(end = [0, leg1])
2782 |> line(end = [leg2, 0])
2783 |> line(end = [0, -thickness])
2784 |> line(end = [-leg2 + thickness, 0])
2785"#;
2786 parse_execute(ast).await.unwrap();
2787 }
2788
2789 #[tokio::test(flavor = "multi_thread")]
2790 async fn test_execute_function_no_return() {
2791 let ast = r#"fn test(@origin) {
2792 origin
2793}
2794
2795test([0, 0])
2796"#;
2797 let result = parse_execute(ast).await;
2798 assert!(result.is_err());
2799 assert!(result.unwrap_err().to_string().contains("undefined"));
2800 }
2801
2802 #[tokio::test(flavor = "multi_thread")]
2803 async fn test_max_stack_size_exceeded_error() {
2804 let ast = r#"
2805fn forever(@n) {
2806 return 1 + forever(n)
2807}
2808
2809forever(1)
2810"#;
2811 let result = parse_execute(ast).await;
2812 let err = result.unwrap_err();
2813 assert!(err.to_string().contains("stack size exceeded"), "actual: {:?}", err);
2814 }
2815
2816 #[tokio::test(flavor = "multi_thread")]
2817 async fn test_math_doubly_nested_parens() {
2818 let ast = r#"sigmaAllow = 35000 // psi
2819width = 4 // inch
2820p = 150 // Force on shelf - lbs
2821distance = 6 // inches
2822FOS = 2
2823leg1 = 5 // inches
2824leg2 = 8 // inches
2825thickness_squared = (distance * p * FOS * 6 / (sigmaAllow - width))
2826thickness = 0.32 // inches. App does not support square root function yet
2827bracket = startSketchOn(XY)
2828 |> startProfile(at = [0,0])
2829 |> line(end = [0, leg1])
2830 |> line(end = [leg2, 0])
2831 |> line(end = [0, -thickness])
2832 |> line(end = [-1 * leg2 + thickness, 0])
2833 |> line(end = [0, -1 * leg1 + thickness])
2834 |> close()
2835 |> extrude(length = width)
2836"#;
2837 parse_execute(ast).await.unwrap();
2838 }
2839
2840 #[tokio::test(flavor = "multi_thread")]
2841 async fn test_math_nested_parens_one_less() {
2842 let ast = r#" sigmaAllow = 35000 // psi
2843width = 4 // inch
2844p = 150 // Force on shelf - lbs
2845distance = 6 // inches
2846FOS = 2
2847leg1 = 5 // inches
2848leg2 = 8 // inches
2849thickness_squared = distance * p * FOS * 6 / (sigmaAllow - width)
2850thickness = 0.32 // inches. App does not support square root function yet
2851bracket = startSketchOn(XY)
2852 |> startProfile(at = [0,0])
2853 |> line(end = [0, leg1])
2854 |> line(end = [leg2, 0])
2855 |> line(end = [0, -thickness])
2856 |> line(end = [-1 * leg2 + thickness, 0])
2857 |> line(end = [0, -1 * leg1 + thickness])
2858 |> close()
2859 |> extrude(length = width)
2860"#;
2861 parse_execute(ast).await.unwrap();
2862 }
2863
2864 #[tokio::test(flavor = "multi_thread")]
2865 async fn test_fn_as_operand() {
2866 let ast = r#"fn f() { return 1 }
2867x = f()
2868y = x + 1
2869z = f() + 1
2870w = f() + f()
2871"#;
2872 parse_execute(ast).await.unwrap();
2873 }
2874
2875 #[tokio::test(flavor = "multi_thread")]
2876 async fn kcl_test_ids_stable_between_executions() {
2877 let code = r#"sketch001 = startSketchOn(XZ)
2878|> startProfile(at = [61.74, 206.13])
2879|> xLine(length = 305.11, tag = $seg01)
2880|> yLine(length = -291.85)
2881|> xLine(length = -segLen(seg01))
2882|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
2883|> close()
2884|> extrude(length = 40.14)
2885|> shell(
2886 thickness = 3.14,
2887 faces = [seg01]
2888)
2889"#;
2890
2891 let ctx = crate::test_server::new_context(true, None).await.unwrap();
2892 let old_program = crate::Program::parse_no_errs(code).unwrap();
2893
2894 if let Err(err) = ctx.run_with_caching(old_program).await {
2896 let report = err.into_miette_report_with_outputs(code).unwrap();
2897 let report = miette::Report::new(report);
2898 panic!("Error executing program: {report:?}");
2899 }
2900
2901 let id_generator = cache::read_old_ast().await.unwrap().main.exec_state.id_generator;
2903
2904 let code = r#"sketch001 = startSketchOn(XZ)
2905|> startProfile(at = [62.74, 206.13])
2906|> xLine(length = 305.11, tag = $seg01)
2907|> yLine(length = -291.85)
2908|> xLine(length = -segLen(seg01))
2909|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
2910|> close()
2911|> extrude(length = 40.14)
2912|> shell(
2913 faces = [seg01],
2914 thickness = 3.14,
2915)
2916"#;
2917
2918 let program = crate::Program::parse_no_errs(code).unwrap();
2920 ctx.run_with_caching(program).await.unwrap();
2922
2923 let new_id_generator = cache::read_old_ast().await.unwrap().main.exec_state.id_generator;
2924
2925 assert_eq!(id_generator, new_id_generator);
2926 }
2927
2928 #[tokio::test(flavor = "multi_thread")]
2929 async fn kcl_test_changing_a_setting_updates_the_cached_state() {
2930 let code = r#"sketch001 = startSketchOn(XZ)
2931|> startProfile(at = [61.74, 206.13])
2932|> xLine(length = 305.11, tag = $seg01)
2933|> yLine(length = -291.85)
2934|> xLine(length = -segLen(seg01))
2935|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
2936|> close()
2937|> extrude(length = 40.14)
2938|> shell(
2939 thickness = 3.14,
2940 faces = [seg01]
2941)
2942"#;
2943
2944 let mut ctx = crate::test_server::new_context(true, None).await.unwrap();
2945 let old_program = crate::Program::parse_no_errs(code).unwrap();
2946
2947 ctx.run_with_caching(old_program.clone()).await.unwrap();
2949
2950 let settings_state = cache::read_old_ast().await.unwrap().settings;
2951
2952 assert_eq!(settings_state, ctx.settings);
2954
2955 ctx.settings.highlight_edges = !ctx.settings.highlight_edges;
2957
2958 ctx.run_with_caching(old_program.clone()).await.unwrap();
2960
2961 let settings_state = cache::read_old_ast().await.unwrap().settings;
2962
2963 assert_eq!(settings_state, ctx.settings);
2965
2966 ctx.settings.highlight_edges = !ctx.settings.highlight_edges;
2968
2969 ctx.run_with_caching(old_program).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.close().await;
2978 }
2979
2980 #[tokio::test(flavor = "multi_thread")]
2981 async fn mock_after_not_mock() {
2982 let ctx = ExecutorContext::new_with_default_client().await.unwrap();
2983 let program = crate::Program::parse_no_errs("x = 2").unwrap();
2984 let result = ctx.run_with_caching(program).await.unwrap();
2985 assert_eq!(result.variables.get("x").unwrap().as_f64().unwrap(), 2.0);
2986
2987 let ctx2 = ExecutorContext::new_mock(None).await;
2988 let program2 = crate::Program::parse_no_errs("z = x + 1").unwrap();
2989 let result = ctx2.run_mock(&program2, &MockConfig::default()).await.unwrap();
2990 assert_eq!(result.variables.get("z").unwrap().as_f64().unwrap(), 3.0);
2991
2992 ctx.close().await;
2993 ctx2.close().await;
2994 }
2995
2996 #[tokio::test(flavor = "multi_thread")]
2997 async fn mock_then_add_extrude_then_mock_again() {
2998 let code = "@settings(experimentalFeatures = allow)
2999
3000s = sketch(on = XY) {
3001 line1 = line(start = [0.05, 0.05], end = [3.88, 0.81])
3002 line2 = line(start = [3.88, 0.81], end = [0.92, 4.67])
3003 coincident([line1.end, line2.start])
3004 line3 = line(start = [0.92, 4.67], end = [0.05, 0.05])
3005 coincident([line2.end, line3.start])
3006 coincident([line1.start, line3.end])
3007}
3008 ";
3009 let ctx = ExecutorContext::new_mock(None).await;
3010 let program = crate::Program::parse_no_errs(code).unwrap();
3011 let result = ctx.run_mock(&program, &MockConfig::default()).await.unwrap();
3012 assert!(result.variables.contains_key("s"), "actual: {:?}", &result.variables);
3013
3014 let code2 = code.to_owned()
3015 + "
3016region001 = region(point = [1mm, 1mm], sketch = s)
3017extrude001 = extrude(region001, length = 1)
3018 ";
3019 let program2 = crate::Program::parse_no_errs(&code2).unwrap();
3020 let result = ctx.run_mock(&program2, &MockConfig::default()).await.unwrap();
3021 assert!(
3022 result.variables.contains_key("region001"),
3023 "actual: {:?}",
3024 &result.variables
3025 );
3026
3027 ctx.close().await;
3028 }
3029
3030 #[cfg(feature = "artifact-graph")]
3031 #[tokio::test(flavor = "multi_thread")]
3032 async fn mock_has_stable_ids() {
3033 let ctx = ExecutorContext::new_mock(None).await;
3034 let mock_config = MockConfig {
3035 use_prev_memory: false,
3036 ..Default::default()
3037 };
3038 let code = "sk = startSketchOn(XY)
3039 |> startProfile(at = [0, 0])";
3040 let program = crate::Program::parse_no_errs(code).unwrap();
3041 let result = ctx.run_mock(&program, &mock_config).await.unwrap();
3042 let ids = result.artifact_graph.iter().map(|(k, _)| *k).collect::<Vec<_>>();
3043 assert!(!ids.is_empty(), "IDs should not be empty");
3044
3045 let ctx2 = ExecutorContext::new_mock(None).await;
3046 let program2 = crate::Program::parse_no_errs(code).unwrap();
3047 let result = ctx2.run_mock(&program2, &mock_config).await.unwrap();
3048 let ids2 = result.artifact_graph.iter().map(|(k, _)| *k).collect::<Vec<_>>();
3049
3050 assert_eq!(ids, ids2, "Generated IDs should match");
3051 ctx.close().await;
3052 ctx2.close().await;
3053 }
3054
3055 #[cfg(feature = "artifact-graph")]
3056 #[tokio::test(flavor = "multi_thread")]
3057 async fn sim_sketch_mode_real_mock_real() {
3058 let ctx = ExecutorContext::new_with_default_client().await.unwrap();
3059 let code = r#"sketch001 = startSketchOn(XY)
3060profile001 = startProfile(sketch001, at = [0, 0])
3061 |> line(end = [10, 0])
3062 |> line(end = [0, 10])
3063 |> line(end = [-10, 0])
3064 |> line(end = [0, -10])
3065 |> close()
3066"#;
3067 let program = crate::Program::parse_no_errs(code).unwrap();
3068 let result = ctx.run_with_caching(program).await.unwrap();
3069 assert_eq!(result.operations.len(), 1);
3070
3071 let mock_ctx = ExecutorContext::new_mock(None).await;
3072 let mock_program = crate::Program::parse_no_errs(code).unwrap();
3073 let mock_result = mock_ctx.run_mock(&mock_program, &MockConfig::default()).await.unwrap();
3074 assert_eq!(mock_result.operations.len(), 1);
3075
3076 let code2 = code.to_owned()
3077 + r#"
3078extrude001 = extrude(profile001, length = 10)
3079"#;
3080 let program2 = crate::Program::parse_no_errs(&code2).unwrap();
3081 let result = ctx.run_with_caching(program2).await.unwrap();
3082 assert_eq!(result.operations.len(), 2);
3083
3084 ctx.close().await;
3085 mock_ctx.close().await;
3086 }
3087
3088 #[tokio::test(flavor = "multi_thread")]
3089 async fn read_tag_version() {
3090 let ast = r#"fn bar(@t) {
3091 return startSketchOn(XY)
3092 |> startProfile(at = [0,0])
3093 |> angledLine(
3094 angle = -60,
3095 length = segLen(t),
3096 )
3097 |> line(end = [0, 0])
3098 |> close()
3099}
3100
3101sketch = startSketchOn(XY)
3102 |> startProfile(at = [0,0])
3103 |> line(end = [0, 10])
3104 |> line(end = [10, 0], tag = $tag0)
3105 |> line(endAbsolute = [0, 0])
3106
3107fn foo() {
3108 // tag0 tags an edge
3109 return bar(tag0)
3110}
3111
3112solid = sketch |> extrude(length = 10)
3113// tag0 tags a face
3114sketch2 = startSketchOn(solid, face = tag0)
3115 |> startProfile(at = [0,0])
3116 |> line(end = [0, 1])
3117 |> line(end = [1, 0])
3118 |> line(end = [0, 0])
3119
3120foo() |> extrude(length = 1)
3121"#;
3122 parse_execute(ast).await.unwrap();
3123 }
3124
3125 #[tokio::test(flavor = "multi_thread")]
3126 async fn experimental() {
3127 let code = r#"
3128startSketchOn(XY)
3129 |> startProfile(at = [0, 0], tag = $start)
3130 |> elliptic(center = [0, 0], angleStart = segAng(start), angleEnd = 160deg, majorRadius = 2, minorRadius = 3)
3131"#;
3132 let result = parse_execute(code).await.unwrap();
3133 let errors = result.exec_state.errors();
3134 assert_eq!(errors.len(), 1);
3135 assert_eq!(errors[0].severity, Severity::Error);
3136 let msg = &errors[0].message;
3137 assert!(msg.contains("experimental"), "found {msg}");
3138
3139 let code = r#"@settings(experimentalFeatures = allow)
3140startSketchOn(XY)
3141 |> startProfile(at = [0, 0], tag = $start)
3142 |> elliptic(center = [0, 0], angleStart = segAng(start), angleEnd = 160deg, majorRadius = 2, minorRadius = 3)
3143"#;
3144 let result = parse_execute(code).await.unwrap();
3145 let errors = result.exec_state.errors();
3146 assert!(errors.is_empty());
3147
3148 let code = r#"@settings(experimentalFeatures = warn)
3149startSketchOn(XY)
3150 |> startProfile(at = [0, 0], tag = $start)
3151 |> elliptic(center = [0, 0], angleStart = segAng(start), angleEnd = 160deg, majorRadius = 2, minorRadius = 3)
3152"#;
3153 let result = parse_execute(code).await.unwrap();
3154 let errors = result.exec_state.errors();
3155 assert_eq!(errors.len(), 1);
3156 assert_eq!(errors[0].severity, Severity::Warning);
3157 let msg = &errors[0].message;
3158 assert!(msg.contains("experimental"), "found {msg}");
3159
3160 let code = r#"@settings(experimentalFeatures = deny)
3161startSketchOn(XY)
3162 |> startProfile(at = [0, 0], tag = $start)
3163 |> elliptic(center = [0, 0], angleStart = segAng(start), angleEnd = 160deg, majorRadius = 2, minorRadius = 3)
3164"#;
3165 let result = parse_execute(code).await.unwrap();
3166 let errors = result.exec_state.errors();
3167 assert_eq!(errors.len(), 1);
3168 assert_eq!(errors[0].severity, Severity::Error);
3169 let msg = &errors[0].message;
3170 assert!(msg.contains("experimental"), "found {msg}");
3171
3172 let code = r#"@settings(experimentalFeatures = foo)
3173startSketchOn(XY)
3174 |> startProfile(at = [0, 0], tag = $start)
3175 |> elliptic(center = [0, 0], angleStart = segAng(start), angleEnd = 160deg, majorRadius = 2, minorRadius = 3)
3176"#;
3177 parse_execute(code).await.unwrap_err();
3178 }
3179
3180 #[tokio::test(flavor = "multi_thread")]
3181 async fn experimental_parameter() {
3182 let code = r#"
3183fn inc(@x, @(experimental = true) amount? = 1) {
3184 return x + amount
3185}
3186
3187answer = inc(5, amount = 2)
3188"#;
3189 let result = parse_execute(code).await.unwrap();
3190 let errors = result.exec_state.errors();
3191 assert_eq!(errors.len(), 1);
3192 assert_eq!(errors[0].severity, Severity::Error);
3193 let msg = &errors[0].message;
3194 assert!(msg.contains("experimental"), "found {msg}");
3195
3196 let code = r#"
3198fn inc(@x, @(experimental = true) amount? = 1) {
3199 return x + amount
3200}
3201
3202answer = inc(5)
3203"#;
3204 let result = parse_execute(code).await.unwrap();
3205 let errors = result.exec_state.errors();
3206 assert_eq!(errors.len(), 0);
3207 }
3208
3209 #[tokio::test(flavor = "multi_thread")]
3213 async fn test_tangent_line_arc_executes_with_mock_engine() {
3214 let code = std::fs::read_to_string("tests/tangent_line_arc/input.kcl").unwrap();
3215 parse_execute(&code).await.unwrap();
3216 }
3217
3218 #[tokio::test(flavor = "multi_thread")]
3219 async fn test_tangent_arc_arc_math_only_executes_with_mock_engine() {
3220 let code = std::fs::read_to_string("tests/tangent_arc_arc_math_only/input.kcl").unwrap();
3221 parse_execute(&code).await.unwrap();
3222 }
3223
3224 #[tokio::test(flavor = "multi_thread")]
3225 async fn test_tangent_line_circle_executes_with_mock_engine() {
3226 let code = std::fs::read_to_string("tests/tangent_line_circle/input.kcl").unwrap();
3227 parse_execute(&code).await.unwrap();
3228 }
3229
3230 #[tokio::test(flavor = "multi_thread")]
3231 async fn test_tangent_circle_circle_native_executes_with_mock_engine() {
3232 let code = std::fs::read_to_string("tests/tangent_circle_circle_native/input.kcl").unwrap();
3233 parse_execute(&code).await.unwrap();
3234 }
3235
3236 }