1use std::collections::BTreeMap;
2use std::str::FromStr;
3use std::sync::Arc;
4
5use ahash::AHashMap;
6use anyhow::Result;
7use indexmap::IndexMap;
8use kcl_api::UnitAngle;
9use kcl_api::UnitLength;
10use serde::Deserialize;
11use serde::Serialize;
12use uuid::Uuid;
13
14use crate::CompilationIssue;
15use crate::ExecutorContext;
16use crate::KclErrorWithOutputs;
17use crate::MockConfig;
18use crate::NodePath;
19use crate::SegmentDragAnchor;
20use crate::SourceRange;
21use crate::collections::AhashIndexSet;
22use crate::engine::engine_manager::EngineManager;
23use crate::errors::KclError;
24use crate::errors::KclErrorDetails;
25use crate::errors::Severity;
26use crate::exec::DefaultPlanes;
27use crate::execution::Artifact;
28use crate::execution::ArtifactCommand;
29use crate::execution::ArtifactGraph;
30use crate::execution::ArtifactId;
31use crate::execution::EnvironmentRef;
32use crate::execution::ExecOutcome;
33use crate::execution::ExecutorSettings;
34use crate::execution::KclValue;
35use crate::execution::KclValueView;
36use crate::execution::OperationCallbackArgs;
37use crate::execution::OperationsByModule;
38use crate::execution::ProgramLookup;
39use crate::execution::SketchVarId;
40use crate::execution::UnsolvedSegment;
41use crate::execution::annotations;
42use crate::execution::cad_op::Operation;
43use crate::execution::id_generator::IdGenerator;
44#[cfg(test)]
45use crate::execution::memory::MemoryBackendKind;
46use crate::execution::memory::ProgramMemory;
47use crate::execution::memory::Stack;
48use crate::execution::sketch_solve::Solved;
49use crate::execution::types::NumericType;
50use crate::front::Number;
51use crate::front::Object;
52use crate::front::ObjectId;
53use crate::front::ObjectKind;
54use crate::id::IncIdGenerator;
55use crate::modules::ModuleId;
56use crate::modules::ModuleInfo;
57use crate::modules::ModuleLoader;
58use crate::modules::ModulePath;
59use crate::modules::ModuleRepr;
60use crate::modules::ModuleSource;
61use crate::parsing::ast::types::Annotation;
62use crate::parsing::ast::types::NodeRef;
63use crate::parsing::ast::types::TagNode;
64
65#[derive(Debug, Clone)]
67pub struct ExecState {
68 pub(super) execution_callbacks: Option<std::sync::Arc<dyn crate::execution::ExecutionCallbacks>>,
69 pub(super) global: GlobalState,
70 pub(super) mod_local: ModuleState,
71}
72
73pub type ModuleInfoMap = IndexMap<ModuleId, ModuleInfo>;
74
75#[derive(Debug, Clone)]
76pub(super) struct GlobalState {
77 pub path_to_source_id: IndexMap<ModulePath, ModuleId>,
79 pub id_to_source: IndexMap<ModuleId, ModuleSource>,
81 pub module_infos: ModuleInfoMap,
83 pub mod_loader: ModuleLoader,
85 pub issues: Vec<CompilationIssue>,
87 pub artifacts: ArtifactState,
89 pub root_module_artifacts: ModuleArtifactState,
91 pub segment_ids_edited: AhashIndexSet<ObjectId>,
93 pub drag_anchors: Vec<SegmentDragAnchor>,
95}
96
97impl GlobalState {
98 pub(crate) fn operations_by_module(&self) -> OperationsByModule {
99 let mut operations = OperationsByModule::default();
100 operations.insert(ModuleId::default(), self.root_module_artifacts.operations.clone());
101
102 for (module_id, module_info) in &self.module_infos {
103 match &module_info.repr {
104 ModuleRepr::Root => {}
105 ModuleRepr::Kcl(_, Some(outcome)) => {
106 operations.insert(*module_id, outcome.artifacts.operations.clone());
107 }
108 ModuleRepr::Foreign(_, Some((_, artifacts))) => {
109 operations.insert(*module_id, artifacts.operations.clone());
110 }
111 ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
112 }
113 }
114
115 operations
116 }
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
120pub(crate) enum ConstraintKey {
121 LineCircle([usize; 10]),
122 CircleCircle([usize; 12]),
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126pub(crate) enum TangencyMode {
127 LineCircle(ezpz::LineSide),
128 CircleCircle(ezpz::CircleSide),
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub(crate) enum ConstraintState {
133 Tangency(TangencyMode),
134}
135
136#[derive(Debug, Clone, Default)]
137pub(super) struct ArtifactState {
138 pub artifacts: IndexMap<ArtifactId, Artifact>,
141 pub graph: ArtifactGraph,
143}
144
145#[derive(Debug, Clone, Default, PartialEq, Serialize)]
147pub struct ModuleArtifactState {
148 pub artifacts: IndexMap<ArtifactId, Artifact>,
150 #[serde(skip)]
153 pub unprocessed_commands: Vec<ArtifactCommand>,
154 pub commands: Vec<ArtifactCommand>,
156 #[cfg(feature = "snapshot-engine-responses")]
158 pub responses: IndexMap<Uuid, kittycad_modeling_cmds::websocket::WebSocketResponse>,
159 pub operations: Vec<Operation>,
162 pub object_id_generator: IncIdGenerator<usize>,
164 pub scene_objects: Vec<Object>,
166 pub source_range_to_object: BTreeMap<SourceRange, ObjectId>,
169 pub artifact_id_to_scene_object: IndexMap<ArtifactId, ObjectId>,
171 pub var_solutions: Vec<(SourceRange, Option<NodePath>, Number)>,
173}
174
175#[derive(Debug, Clone)]
176pub(super) struct ModuleState {
177 pub module_id: ModuleId,
179 pub id_generator: IdGenerator,
181 pub stack: Stack,
182 pub(super) call_stack_size: usize,
186 pub pipe_value: Option<KclValue>,
189 pub being_declared: Option<String>,
193 pub sketch_block: Option<SketchBlockState>,
195 pub inside_stdlib: bool,
198 pub stdlib_entry_source_range: Option<SourceRange>,
200 pub module_exports: Vec<String>,
202 pub settings: MetaSettings,
204 pub sketch_mode: bool,
207 pub freedom_analysis: bool,
211 pub(super) explicit_length_units: bool,
212 pub(super) path: ModulePath,
213 pub artifacts: ModuleArtifactState,
215 pub constraint_state: IndexMap<ObjectId, IndexMap<ConstraintKey, ConstraintState>>,
219
220 pub(super) allowed_warnings: Vec<&'static str>,
221 pub(super) denied_warnings: Vec<&'static str>,
222
223 pub(super) consumed_solids: AHashMap<ConsumedSolidKey, ConsumedSolidInfo>,
228 pub(super) consumed_solid_ids: AHashMap<Uuid, ConsumedSolidInfo>,
234}
235
236#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
238pub(crate) struct ConsumedSolidKey {
239 engine_id: Uuid,
241 instance_id: Uuid,
244}
245
246impl ConsumedSolidKey {
247 pub(crate) fn new(engine_id: Uuid, instance_id: Uuid) -> Self {
248 Self { engine_id, instance_id }
249 }
250
251 pub(crate) fn engine_id(&self) -> Uuid {
252 self.engine_id
253 }
254
255 pub(crate) fn instance_id(&self) -> Uuid {
256 self.instance_id
257 }
258}
259
260#[derive(Debug, Clone)]
264pub(crate) struct ConsumedSolidInfo {
265 operation: ConsumedSolidOperation,
267 suggested_replacement_key: Option<ConsumedSolidKey>,
271 returned_solid_keys: Vec<ConsumedSolidKey>,
274}
275
276impl ConsumedSolidInfo {
277 pub(crate) fn new(operation: ConsumedSolidOperation, returned_solid_keys: Vec<ConsumedSolidKey>) -> Self {
278 Self {
279 operation,
280 suggested_replacement_key: returned_solid_keys.first().copied(),
281 returned_solid_keys,
282 }
283 }
284
285 pub(crate) fn operation(&self) -> ConsumedSolidOperation {
286 self.operation
287 }
288
289 pub(crate) fn suggested_replacement_key(&self) -> Option<ConsumedSolidKey> {
290 self.suggested_replacement_key
291 }
292
293 pub(crate) fn should_report_reused_engine_id_as_consumed(&self, key: ConsumedSolidKey) -> bool {
294 !self.returned_solid_keys.contains(&key)
295 }
296}
297
298#[derive(Debug, Clone, Copy, PartialEq, Eq)]
299pub(crate) enum ConsumedSolidOperation {
300 Union,
301 Intersect,
302 Subtract,
303 Split,
304 JoinSurfaces,
305}
306
307impl ConsumedSolidOperation {
308 pub(crate) fn indefinite_article(self) -> &'static str {
309 match self {
310 Self::Intersect => "an",
311 Self::Union | Self::Subtract | Self::Split | Self::JoinSurfaces => "a",
312 }
313 }
314}
315
316impl std::fmt::Display for ConsumedSolidOperation {
317 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
318 match self {
319 Self::Union => f.write_str("union"),
320 Self::Intersect => f.write_str("intersect"),
321 Self::Subtract => f.write_str("subtract"),
322 Self::Split => f.write_str("split"),
323 Self::JoinSurfaces => f.write_str("joinSurfaces"),
324 }
325 }
326}
327
328#[derive(Debug, Clone, Default)]
329pub(crate) struct SketchBlockState {
330 pub sketch_vars: Vec<KclValue>,
331 pub sketch_id: Option<ObjectId>,
332 pub sketch_constraints: Vec<ObjectId>,
333 pub solver_constraints: Vec<ezpz::Constraint>,
334 pub solver_optional_constraints: Vec<ezpz::Constraint>,
335 pub needed_by_engine: Vec<UnsolvedSegment>,
336 pub segment_tags: IndexMap<ObjectId, TagNode>,
337}
338
339impl ExecState {
340 pub fn new(exec_context: &super::ExecutorContext) -> Self {
341 ExecState {
342 execution_callbacks: exec_context.execution_callbacks.clone(),
343 global: GlobalState::new(&exec_context.settings, Default::default()),
344 mod_local: ModuleState::new(ModulePath::Main, ProgramMemory::new(), Default::default(), false, true),
345 }
346 }
347
348 #[cfg(test)]
349 pub(crate) fn new_with_memory_backend(exec_context: &super::ExecutorContext, backend: MemoryBackendKind) -> Self {
350 ExecState {
351 execution_callbacks: exec_context.execution_callbacks.clone(),
352 global: GlobalState::new(&exec_context.settings, Default::default()),
353 mod_local: ModuleState::new(
354 ModulePath::Main,
355 ProgramMemory::new_with_backend(backend),
356 Default::default(),
357 false,
358 true,
359 ),
360 }
361 }
362
363 pub fn new_mock(exec_context: &super::ExecutorContext, mock_config: &MockConfig) -> Self {
364 let segment_ids_edited = mock_config.segment_ids_edited.clone();
365 let mut global = GlobalState::new(&exec_context.settings, segment_ids_edited);
366 global.drag_anchors = mock_config.drag_anchors.clone();
367 ExecState {
368 execution_callbacks: exec_context.execution_callbacks.clone(),
369 global,
370 mod_local: ModuleState::new(
371 ModulePath::Main,
372 ProgramMemory::new(),
373 Default::default(),
374 mock_config.sketch_block_id.is_some(),
375 mock_config.freedom_analysis,
376 ),
377 }
378 }
379
380 #[cfg(test)]
381 pub(crate) fn new_mock_with_memory_backend(
382 exec_context: &super::ExecutorContext,
383 mock_config: &MockConfig,
384 backend: MemoryBackendKind,
385 ) -> Self {
386 let segment_ids_edited = mock_config.segment_ids_edited.clone();
387 let mut global = GlobalState::new(&exec_context.settings, segment_ids_edited);
388 global.drag_anchors = mock_config.drag_anchors.clone();
389 ExecState {
390 execution_callbacks: exec_context.execution_callbacks.clone(),
391 global,
392 mod_local: ModuleState::new(
393 ModulePath::Main,
394 ProgramMemory::new_with_backend(backend),
395 Default::default(),
396 mock_config.sketch_block_id.is_some(),
397 mock_config.freedom_analysis,
398 ),
399 }
400 }
401
402 pub(super) fn reset(&mut self, exec_context: &super::ExecutorContext) {
403 let global = GlobalState::new(&exec_context.settings, Default::default());
404
405 *self = ExecState {
406 execution_callbacks: exec_context.execution_callbacks.clone(),
407 global,
408 mod_local: ModuleState::new(
409 self.mod_local.path.clone(),
410 ProgramMemory::new(),
411 Default::default(),
412 false,
413 true,
414 ),
415 };
416 }
417
418 pub fn err(&mut self, e: CompilationIssue) {
420 self.global.issues.push(e);
421 }
422
423 pub fn warn(&mut self, mut e: CompilationIssue, name: &'static str) {
425 debug_assert!(annotations::WARN_VALUES.contains(&name));
426
427 if self.mod_local.allowed_warnings.contains(&name) {
428 return;
429 }
430
431 if self.mod_local.denied_warnings.contains(&name) {
432 e.severity = Severity::Error;
433 } else {
434 e.severity = Severity::Warning;
435 }
436
437 self.global.issues.push(e);
438 }
439
440 pub fn warn_experimental(&mut self, feature_name: &str, source_range: SourceRange) {
441 let Some(severity) = self.mod_local.settings.experimental_features.severity() else {
442 return;
443 };
444 let error = CompilationIssue {
445 source_range,
446 message: format!("Use of {feature_name} is experimental and may change or be removed."),
447 suggestion: None,
448 severity,
449 tag: crate::errors::Tag::None,
450 };
451
452 self.global.issues.push(error);
453 }
454
455 pub fn clear_units_warnings(&mut self, source_range: &SourceRange) {
456 self.global.issues = std::mem::take(&mut self.global.issues)
457 .into_iter()
458 .filter(|e| {
459 e.severity != Severity::Warning
460 || !source_range.contains_range(&e.source_range)
461 || e.tag != crate::errors::Tag::UnknownNumericUnits
462 })
463 .collect();
464 }
465
466 pub fn issues(&self) -> &[CompilationIssue] {
467 &self.global.issues
468 }
469
470 pub async fn into_exec_outcome(
474 self,
475 main_ref: EnvironmentRef,
476 ctx: &ExecutorContext,
477 ) -> Result<ExecOutcome, KclError> {
478 let variables = self
481 .mod_local
482 .variables(main_ref)?
483 .into_iter()
484 .map(|(key, value)| (key, KclValueView::from(value)))
485 .collect();
486 Ok(ExecOutcome {
487 variables,
488 filenames: self.global.filenames(),
489 operations: self.global.operations_by_module(),
490 artifact_graph: self.global.artifacts.graph,
491 scene_objects: self.global.root_module_artifacts.scene_objects,
492 source_range_to_object: self.global.root_module_artifacts.source_range_to_object,
493 var_solutions: self.global.root_module_artifacts.var_solutions,
494 issues: self.global.issues,
495 default_planes: ctx.engine.get_default_planes().read().await.clone(),
496 })
497 }
498
499 #[cfg(feature = "snapshot-engine-responses")]
500 pub(crate) fn take_root_module_responses(
501 &mut self,
502 ) -> IndexMap<Uuid, kittycad_modeling_cmds::websocket::WebSocketResponse> {
503 std::mem::take(&mut self.global.root_module_artifacts.responses)
504 }
505
506 pub(crate) fn stack(&self) -> &Stack {
507 &self.mod_local.stack
508 }
509
510 pub(crate) fn mut_stack(&mut self) -> &mut Stack {
511 &mut self.mod_local.stack
512 }
513
514 pub(super) fn inc_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
517 if self.mod_local.call_stack_size >= 50 {
520 return Err(KclError::MaxCallStack {
521 details: KclErrorDetails::new("maximum call stack size exceeded".to_owned(), vec![range]),
522 });
523 }
524 self.mod_local.call_stack_size += 1;
525 Ok(())
526 }
527
528 pub(super) fn dec_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
531 if self.mod_local.call_stack_size == 0 {
533 let message = "call stack size below zero".to_owned();
534 debug_assert!(false, "{message}");
535 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
536 }
537 self.mod_local.call_stack_size -= 1;
538 Ok(())
539 }
540
541 pub(crate) fn sketch_mode(&self) -> bool {
546 self.mod_local.sketch_mode
547 && match &self.mod_local.path {
548 ModulePath::Main => true,
549 ModulePath::Local { .. } => true,
550 ModulePath::Std { .. } => false,
551 }
552 }
553
554 pub fn next_object_id(&mut self) -> ObjectId {
555 ObjectId(self.mod_local.artifacts.object_id_generator.next_id())
556 }
557
558 pub fn peek_object_id(&self) -> ObjectId {
559 ObjectId(self.mod_local.artifacts.object_id_generator.peek_id())
560 }
561
562 pub(crate) fn constraint_state(&self, sketch_block_id: ObjectId, key: &ConstraintKey) -> Option<ConstraintState> {
563 let map = self.mod_local.constraint_state.get(&sketch_block_id)?;
564 map.get(key).copied()
565 }
566
567 pub(crate) fn set_constraint_state(
568 &mut self,
569 sketch_block_id: ObjectId,
570 key: ConstraintKey,
571 state: ConstraintState,
572 ) {
573 let map = self.mod_local.constraint_state.entry(sketch_block_id).or_default();
574 map.insert(key, state);
575 }
576
577 pub fn add_scene_object(&mut self, obj: Object, source_range: SourceRange) -> ObjectId {
578 let id = obj.id;
579 debug_assert!(
580 id.0 == self.mod_local.artifacts.scene_objects.len(),
581 "Adding scene object with ID {} but next ID is {}",
582 id.0,
583 self.mod_local.artifacts.scene_objects.len()
584 );
585 let artifact_id = obj.artifact_id;
586 self.mod_local.artifacts.scene_objects.push(obj);
587 self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
588 self.mod_local
589 .artifacts
590 .artifact_id_to_scene_object
591 .insert(artifact_id, id);
592 id
593 }
594
595 pub fn add_placeholder_scene_object(
598 &mut self,
599 id: ObjectId,
600 source_range: SourceRange,
601 node_path: Option<NodePath>,
602 ) -> ObjectId {
603 debug_assert!(id.0 == self.mod_local.artifacts.scene_objects.len());
604 self.mod_local
605 .artifacts
606 .scene_objects
607 .push(Object::placeholder(id, source_range, node_path));
608 self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
609 id
610 }
611
612 pub fn set_scene_object(&mut self, object: Object) {
614 let id = object.id;
615 let artifact_id = object.artifact_id;
616 self.mod_local.artifacts.scene_objects[id.0] = object;
617 self.mod_local
618 .artifacts
619 .artifact_id_to_scene_object
620 .insert(artifact_id, id);
621 }
622
623 pub fn scene_object_id_by_artifact_id(&self, artifact_id: ArtifactId) -> Option<ObjectId> {
624 self.mod_local
625 .artifacts
626 .artifact_id_to_scene_object
627 .get(&artifact_id)
628 .cloned()
629 }
630
631 pub fn segment_ids_edited_contains(&self, object_id: &ObjectId) -> bool {
632 self.global.segment_ids_edited.contains(object_id)
633 }
634
635 pub fn drag_anchor_target(&self, object_id: &ObjectId) -> Option<&crate::front::Point2d<crate::front::Number>> {
636 self.global
637 .drag_anchors
638 .iter()
639 .find(|anchor| &anchor.segment_id == object_id)
640 .map(|anchor| &anchor.target)
641 }
642
643 pub(super) fn is_in_sketch_block(&self) -> bool {
644 self.mod_local.sketch_block.is_some()
645 }
646
647 pub(crate) fn sketch_block_mut(&mut self) -> Option<&mut SketchBlockState> {
648 self.mod_local.sketch_block.as_mut()
649 }
650
651 pub(crate) fn sketch_block(&mut self) -> Option<&SketchBlockState> {
652 self.mod_local.sketch_block.as_ref()
653 }
654
655 pub fn next_uuid(&mut self) -> Uuid {
656 self.mod_local.id_generator.next_uuid()
657 }
658
659 pub fn next_artifact_id(&mut self) -> ArtifactId {
660 self.mod_local.id_generator.next_artifact_id()
661 }
662
663 pub fn id_generator(&mut self) -> &mut IdGenerator {
664 &mut self.mod_local.id_generator
665 }
666
667 pub(crate) fn mark_solid_consumed(&mut self, consumed_key: ConsumedSolidKey, info: ConsumedSolidInfo) {
669 self.mod_local.consumed_solids.insert(consumed_key, info);
670 }
671
672 pub(crate) fn mark_solid_id_consumed(&mut self, consumed_id: Uuid, info: ConsumedSolidInfo) {
675 self.mod_local.consumed_solid_ids.insert(consumed_id, info);
676 }
677
678 pub(crate) fn check_solid_consumed(&self, key: &ConsumedSolidKey) -> Option<&ConsumedSolidInfo> {
681 self.mod_local.consumed_solids.get(key)
682 }
683
684 pub(crate) fn check_solid_id_consumed(&self, id: &Uuid) -> Option<&ConsumedSolidInfo> {
687 self.mod_local.consumed_solid_ids.get(id)
688 }
689
690 pub(crate) fn latest_consumed_output(
693 &self,
694 suggested_replacement_key: Option<ConsumedSolidKey>,
695 ) -> Option<ConsumedSolidKey> {
696 let mut latest = suggested_replacement_key?;
697 let mut seen = AhashIndexSet::default();
698
699 while seen.insert(latest) {
700 let Some(next) = self
701 .mod_local
702 .consumed_solids
703 .get(&latest)
704 .and_then(|info| info.suggested_replacement_key())
705 else {
706 break;
707 };
708 latest = next;
709 }
710
711 Some(latest)
712 }
713
714 pub(crate) fn find_var_name_for_solid_key(&self, target_key: ConsumedSolidKey) -> Result<Option<String>, KclError> {
718 fn contains_solid_key(value: &KclValue, target_key: ConsumedSolidKey) -> bool {
719 match value {
720 KclValue::Solid { value } => {
721 value.id == target_key.engine_id() && value.value_id == target_key.instance_id()
722 }
723 KclValue::HomArray { value, .. } => value.iter().any(|v| contains_solid_key(v, target_key)),
724 _ => false,
725 }
726 }
727 self.mod_local
728 .stack
729 .find_var_name_in_all_envs(|value| contains_solid_key(value, target_key))
730 }
731
732 pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
733 let id = artifact.id();
734 self.mod_local.artifacts.artifacts.insert(id, artifact);
735 }
736
737 pub(crate) fn artifact_mut(&mut self, id: ArtifactId) -> Option<&mut Artifact> {
738 self.mod_local.artifacts.artifacts.get_mut(&id)
739 }
740
741 pub(crate) fn push_op(&mut self, op: Operation) {
742 let index = self.mod_local.artifacts.operations.len();
743 self.mod_local.artifacts.operations.push(op);
744 if let Some(operation) = self.mod_local.artifacts.operations.last().cloned()
745 && let Some(callbacks) = &self.execution_callbacks
746 {
747 callbacks.on_operation(OperationCallbackArgs {
748 module_id: self.mod_local.module_id,
749 operation,
750 index,
751 });
752 }
753 }
754
755 pub(crate) fn push_command(&mut self, command: ArtifactCommand) {
756 self.mod_local.artifacts.unprocessed_commands.push(command);
757 }
758
759 pub(super) fn next_module_id(&self) -> ModuleId {
760 ModuleId::from_usize(self.global.path_to_source_id.len())
761 }
762
763 pub(super) fn id_for_module(&self, path: &ModulePath) -> Option<ModuleId> {
764 self.global.path_to_source_id.get(path).cloned()
765 }
766
767 pub(super) fn add_path_to_source_id(&mut self, path: ModulePath, id: ModuleId) {
768 debug_assert!(!self.global.path_to_source_id.contains_key(&path));
769 self.global.path_to_source_id.insert(path, id);
770 }
771
772 pub(crate) fn add_root_module_contents(&mut self, program: &crate::Program) {
773 let root_id = ModuleId::default();
774 let path = self
776 .global
777 .path_to_source_id
778 .iter()
779 .find(|(_, v)| **v == root_id)
780 .unwrap()
781 .0
782 .clone();
783 self.add_id_to_source(
784 root_id,
785 ModuleSource {
786 path,
787 source: program.original_file_contents.to_string(),
788 },
789 );
790 }
791
792 pub(super) fn add_id_to_source(&mut self, id: ModuleId, source: ModuleSource) {
793 self.global.id_to_source.insert(id, source);
794 }
795
796 pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
797 debug_assert!(self.global.path_to_source_id.contains_key(&path));
798 let module_info = ModuleInfo { id, repr, path };
799 self.global.module_infos.insert(id, module_info);
800 }
801
802 pub fn get_module(&mut self, id: ModuleId) -> Option<&ModuleInfo> {
803 self.global.module_infos.get(&id)
804 }
805
806 #[cfg(test)]
807 pub(crate) fn modules(&self) -> &ModuleInfoMap {
808 &self.global.module_infos
809 }
810
811 #[cfg(test)]
812 pub(crate) fn root_module_artifact_state(&self) -> &ModuleArtifactState {
813 &self.global.root_module_artifacts
814 }
815
816 pub fn current_default_units(&self) -> NumericType {
817 NumericType::Default {
818 len: self.length_unit(),
819 angle: self.angle_unit(),
820 }
821 }
822
823 pub fn length_unit(&self) -> UnitLength {
824 self.mod_local.settings.default_length_units
825 }
826
827 pub fn angle_unit(&self) -> UnitAngle {
828 self.mod_local.settings.default_angle_units
829 }
830
831 pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
832 KclError::new_import_cycle(KclErrorDetails::new(
833 format!(
834 "circular import of modules is not allowed: {} -> {}",
835 self.global
836 .mod_loader
837 .import_stack
838 .iter()
839 .map(|p| p.to_string_lossy())
840 .collect::<Vec<_>>()
841 .join(" -> "),
842 path,
843 ),
844 vec![source_range],
845 ))
846 }
847
848 pub(crate) fn pipe_value(&self) -> Option<&KclValue> {
849 self.mod_local.pipe_value.as_ref()
850 }
851
852 pub(crate) fn error_with_outputs(
853 &self,
854 error: KclError,
855 main_ref: Option<EnvironmentRef>,
856 default_planes: Option<DefaultPlanes>,
857 ) -> KclErrorWithOutputs {
858 let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = self
859 .global
860 .path_to_source_id
861 .iter()
862 .map(|(k, v)| ((*v), k.clone()))
863 .collect();
864
865 KclErrorWithOutputs::new(
866 error,
867 self.issues().to_vec(),
868 main_ref
869 .and_then(|main_ref| self.mod_local.variables(main_ref).ok())
870 .unwrap_or_default(),
871 self.global.operations_by_module(),
872 Default::default(),
873 self.global.artifacts.graph.clone(),
874 self.global.root_module_artifacts.scene_objects.clone(),
875 self.global.root_module_artifacts.source_range_to_object.clone(),
876 self.global.root_module_artifacts.var_solutions.clone(),
877 module_id_to_module_path,
878 self.global.id_to_source.clone(),
879 default_planes,
880 )
881 }
882
883 pub(crate) fn build_program_lookup(
884 &self,
885 current: crate::parsing::ast::types::Node<crate::parsing::ast::types::Program>,
886 ) -> ProgramLookup {
887 ProgramLookup::new(current, self.global.module_infos.clone())
888 }
889
890 pub(crate) async fn build_artifact_graph(
891 &mut self,
892 engine: &Arc<EngineManager>,
893 program: NodeRef<'_, crate::parsing::ast::types::Program>,
894 ) -> Result<(), KclError> {
895 let mut new_commands = Vec::new();
896 let mut new_exec_artifacts = IndexMap::new();
897 for module in self.global.module_infos.values_mut() {
898 match &mut module.repr {
899 ModuleRepr::Kcl(_, Some(outcome)) => {
900 new_commands.extend(outcome.artifacts.process_commands());
901 new_exec_artifacts.extend(outcome.artifacts.artifacts.clone());
902 }
903 ModuleRepr::Foreign(_, Some((_, module_artifacts))) => {
904 new_commands.extend(module_artifacts.process_commands());
905 new_exec_artifacts.extend(module_artifacts.artifacts.clone());
906 }
907 ModuleRepr::Root | ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
908 }
909 }
910 new_commands.extend(self.global.root_module_artifacts.process_commands());
913 new_exec_artifacts.extend(self.global.root_module_artifacts.artifacts.clone());
916 let new_responses = engine.take_responses().await;
917
918 for (id, exec_artifact) in new_exec_artifacts {
921 self.global.artifacts.artifacts.entry(id).or_insert(exec_artifact);
925 }
926
927 let initial_graph = self.global.artifacts.graph.clone();
928
929 let programs = self.build_program_lookup(program.clone());
931 let graph_result = crate::execution::artifact::build_artifact_graph(
932 &new_commands,
933 &new_responses,
934 program,
935 &mut self.global.artifacts.artifacts,
936 initial_graph,
937 &programs,
938 &self.global.module_infos,
939 );
940
941 #[cfg(feature = "snapshot-engine-responses")]
942 {
943 self.global.root_module_artifacts.responses.extend(new_responses);
945 }
946
947 let artifact_graph = graph_result?;
948 self.global.artifacts.graph = artifact_graph;
949
950 Ok(())
951 }
952
953 pub(crate) fn kcl_version(&self) -> KclVersion {
954 self.mod_local.settings.kcl_version.parse().unwrap_or_default()
955 }
956}
957
958#[derive(Default)]
959pub enum KclVersion {
960 #[default]
961 V1,
962 V2,
963}
964
965impl FromStr for KclVersion {
966 type Err = KclError;
967
968 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
969 match s {
970 "1" | "1.0" | "1.0.0" => Ok(Self::V1),
971 "2" | "2.0" | "2.0.0" => Ok(Self::V2),
972 other => Err(KclError::new_semantic(KclErrorDetails {
973 source_ranges: Default::default(),
974 backtrace: Default::default(),
975 message: format!("Unrecognized version {other}. Valid versions are 1.0 and 2.0"),
976 })),
977 }
978 }
979}
980
981impl GlobalState {
982 fn new(settings: &ExecutorSettings, segment_ids_edited: AhashIndexSet<ObjectId>) -> Self {
983 let mut global = GlobalState {
984 path_to_source_id: Default::default(),
985 module_infos: Default::default(),
986 artifacts: Default::default(),
987 root_module_artifacts: Default::default(),
988 mod_loader: Default::default(),
989 issues: Default::default(),
990 id_to_source: Default::default(),
991 segment_ids_edited,
992 drag_anchors: Vec::new(),
993 };
994
995 let root_id = ModuleId::default();
996 let root_path = settings.current_file.clone().unwrap_or_default();
997 global.module_infos.insert(
998 root_id,
999 ModuleInfo {
1000 id: root_id,
1001 path: ModulePath::Local {
1002 value: root_path.clone(),
1003 original_import_path: None,
1004 },
1005 repr: ModuleRepr::Root,
1006 },
1007 );
1008 global.path_to_source_id.insert(
1009 ModulePath::Local {
1010 value: root_path,
1011 original_import_path: None,
1012 },
1013 root_id,
1014 );
1015 global
1016 }
1017
1018 pub(super) fn filenames(&self) -> IndexMap<ModuleId, ModulePath> {
1019 self.path_to_source_id.iter().map(|(k, v)| ((*v), k.clone())).collect()
1020 }
1021
1022 pub(super) fn get_source(&self, id: ModuleId) -> Option<&ModuleSource> {
1023 self.id_to_source.get(&id)
1024 }
1025}
1026
1027impl ArtifactState {
1028 pub fn cached_body_items(&self) -> usize {
1029 self.graph.item_count
1030 }
1031
1032 pub(crate) fn clear(&mut self) {
1033 self.artifacts.clear();
1034 self.graph.clear();
1035 }
1036}
1037
1038impl ModuleArtifactState {
1039 pub(crate) fn clear(&mut self) {
1040 self.artifacts.clear();
1041 self.unprocessed_commands.clear();
1042 self.commands.clear();
1043 self.operations.clear();
1044 }
1045
1046 pub(crate) fn restore_scene_objects(&mut self, scene_objects: &[Object]) {
1047 self.scene_objects = scene_objects.to_vec();
1048 self.object_id_generator = IncIdGenerator::new(self.scene_objects.len());
1049 self.source_range_to_object.clear();
1050 self.artifact_id_to_scene_object.clear();
1051
1052 for (expected_id, object) in self.scene_objects.iter().enumerate() {
1053 debug_assert_eq!(
1054 object.id.0, expected_id,
1055 "Restored cached scene object ID {} does not match its position {}",
1056 object.id.0, expected_id
1057 );
1058
1059 match &object.kind {
1060 ObjectKind::Wall(wall) => {
1061 self.source_range_to_object.insert(wall.source.solid.range, object.id);
1062 }
1063 ObjectKind::Cap(cap) => {
1064 self.source_range_to_object.insert(cap.source.solid.range, object.id);
1065 }
1066 _ => match &object.source {
1067 crate::front::SourceRef::Simple { range, node_path: _ } => {
1068 self.source_range_to_object.insert(*range, object.id);
1069 }
1070 crate::front::SourceRef::BackTrace { ranges } => {
1071 if let Some((range, _)) = ranges.first() {
1074 self.source_range_to_object.insert(*range, object.id);
1075 }
1076 }
1077 },
1078 }
1079
1080 if object.artifact_id != ArtifactId::placeholder() {
1082 self.artifact_id_to_scene_object.insert(object.artifact_id, object.id);
1083 }
1084 }
1085 }
1086
1087 pub(crate) fn extend(&mut self, other: ModuleArtifactState) {
1089 self.artifacts.extend(other.artifacts);
1090 self.unprocessed_commands.extend(other.unprocessed_commands);
1091 self.commands.extend(other.commands);
1092 self.operations.extend(other.operations);
1093 if other.scene_objects.len() > self.scene_objects.len() {
1094 self.scene_objects
1095 .extend(other.scene_objects[self.scene_objects.len()..].iter().cloned());
1096 }
1097 self.source_range_to_object.extend(other.source_range_to_object);
1098 self.artifact_id_to_scene_object
1099 .extend(other.artifact_id_to_scene_object);
1100 self.var_solutions.extend(other.var_solutions);
1101 }
1102
1103 pub(crate) fn process_commands(&mut self) -> Vec<ArtifactCommand> {
1107 let unprocessed = std::mem::take(&mut self.unprocessed_commands);
1108 let new_module_commands = unprocessed.clone();
1109 self.commands.extend(unprocessed);
1110 new_module_commands
1111 }
1112
1113 pub(crate) fn scene_object_by_id(&self, id: ObjectId) -> Option<&Object> {
1114 debug_assert!(
1115 id.0 < self.scene_objects.len(),
1116 "Requested object ID {} but only have {} objects",
1117 id.0,
1118 self.scene_objects.len()
1119 );
1120 self.scene_objects.get(id.0)
1121 }
1122
1123 pub(crate) fn scene_object_by_id_mut(&mut self, id: ObjectId) -> Option<&mut Object> {
1124 debug_assert!(
1125 id.0 < self.scene_objects.len(),
1126 "Requested object ID {} but only have {} objects",
1127 id.0,
1128 self.scene_objects.len()
1129 );
1130 self.scene_objects.get_mut(id.0)
1131 }
1132}
1133
1134impl ModuleState {
1135 pub(super) fn new(
1136 path: ModulePath,
1137 memory: Arc<ProgramMemory>,
1138 module_id: Option<ModuleId>,
1139 sketch_mode: bool,
1140 freedom_analysis: bool,
1141 ) -> Self {
1142 let state_module_id = module_id.unwrap_or_default();
1143 ModuleState {
1144 module_id: state_module_id,
1145 id_generator: IdGenerator::new(module_id),
1146 stack: memory.new_stack(),
1147 call_stack_size: 0,
1148 pipe_value: Default::default(),
1149 being_declared: Default::default(),
1150 sketch_block: Default::default(),
1151 stdlib_entry_source_range: Default::default(),
1152 module_exports: Default::default(),
1153 explicit_length_units: false,
1154 path,
1155 settings: Default::default(),
1156 sketch_mode,
1157 freedom_analysis,
1158 artifacts: Default::default(),
1159 constraint_state: Default::default(),
1160 allowed_warnings: Vec::new(),
1161 denied_warnings: Vec::new(),
1162 consumed_solids: AHashMap::default(),
1163 consumed_solid_ids: AHashMap::default(),
1164 inside_stdlib: false,
1165 }
1166 }
1167
1168 pub(super) fn variables(&self, main_ref: EnvironmentRef) -> Result<IndexMap<String, KclValue>, KclError> {
1169 self.stack.find_all_in_env_owned(main_ref)
1170 }
1171}
1172
1173impl SketchBlockState {
1174 pub(crate) fn next_sketch_var_id(&self) -> SketchVarId {
1175 SketchVarId(self.sketch_vars.len())
1176 }
1177
1178 pub(crate) fn var_solutions(
1181 &self,
1182 solve_outcome: &Solved,
1183 solution_ty: NumericType,
1184 sketch_block_range: SourceRange,
1185 ) -> Result<Vec<(SourceRange, Option<NodePath>, Number)>, KclError> {
1186 self.sketch_vars
1187 .iter()
1188 .map(|v| {
1189 let Some(sketch_var) = v.as_sketch_var() else {
1190 return Err(KclError::new_internal(KclErrorDetails::new(
1191 "Expected sketch variable".to_owned(),
1192 vec![sketch_block_range],
1193 )));
1194 };
1195 let var_index = sketch_var.id.0;
1196 let solved_n = solve_outcome.final_values.get(var_index).ok_or_else(|| {
1197 let message = format!("No solution for sketch variable with id {}", var_index);
1198 debug_assert!(false, "{}", &message);
1199 KclError::new_internal(KclErrorDetails::new(
1200 message,
1201 sketch_var.meta.iter().map(|m| m.source_range).collect(),
1202 ))
1203 })?;
1204 let solved_value = Number {
1205 value: *solved_n,
1206 units: solution_ty.try_into().map_err(|_| {
1207 KclError::new_internal(KclErrorDetails::new(
1208 "Failed to convert numeric type to units".to_owned(),
1209 vec![sketch_block_range],
1210 ))
1211 })?,
1212 };
1213 let Some(source_range) = sketch_var.meta.first().map(|m| m.source_range) else {
1214 return Ok(None);
1215 };
1216 Ok(Some((source_range, sketch_var.node_path.clone(), solved_value)))
1217 })
1218 .filter_map(Result::transpose)
1219 .collect::<Result<Vec<_>, KclError>>()
1220 }
1221}
1222
1223#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
1224#[ts(export)]
1225#[serde(rename_all = "camelCase")]
1226pub struct MetaSettings {
1227 pub default_length_units: UnitLength,
1228 pub default_angle_units: UnitAngle,
1229 pub experimental_features: annotations::WarningLevel,
1230 pub kcl_version: String,
1231}
1232
1233impl Default for MetaSettings {
1234 fn default() -> Self {
1235 MetaSettings {
1236 default_length_units: UnitLength::Millimeters,
1237 default_angle_units: UnitAngle::Degrees,
1238 experimental_features: annotations::WarningLevel::Deny,
1239 kcl_version: "1.0".to_owned(),
1240 }
1241 }
1242}
1243
1244impl MetaSettings {
1245 pub(crate) fn update_from_annotation(
1246 &mut self,
1247 annotation: &crate::parsing::ast::types::Node<Annotation>,
1248 ) -> Result<(bool, bool), KclError> {
1249 let properties = annotations::expect_properties(annotations::SETTINGS, annotation)?;
1250
1251 let mut updated_len = false;
1252 let mut updated_angle = false;
1253 for p in properties {
1254 match &*p.inner.key.name {
1255 annotations::SETTINGS_UNIT_LENGTH => {
1256 let value = annotations::expect_ident(&p.inner.value)?;
1257 let value = super::types::length_from_str(value, annotation.as_source_range())?;
1258 self.default_length_units = value;
1259 updated_len = true;
1260 }
1261 annotations::SETTINGS_UNIT_ANGLE => {
1262 let value = annotations::expect_ident(&p.inner.value)?;
1263 let value = super::types::angle_from_str(value, annotation.as_source_range())?;
1264 self.default_angle_units = value;
1265 updated_angle = true;
1266 }
1267 annotations::SETTINGS_VERSION => {
1268 let value = annotations::expect_number(&p.inner.value)?;
1269 self.kcl_version = value;
1270 }
1271 annotations::SETTINGS_EXPERIMENTAL_FEATURES => {
1272 let value = annotations::expect_ident(&p.inner.value)?;
1273 let value = annotations::WarningLevel::from_str(value).map_err(|_| {
1274 KclError::new_semantic(KclErrorDetails::new(
1275 format!(
1276 "Invalid value for {} settings property, expected one of: {}",
1277 annotations::SETTINGS_EXPERIMENTAL_FEATURES,
1278 annotations::WARN_LEVELS.join(", ")
1279 ),
1280 annotation.as_source_ranges(),
1281 ))
1282 })?;
1283 self.experimental_features = value;
1284 }
1285 name => {
1286 return Err(KclError::new_semantic(KclErrorDetails::new(
1287 format!(
1288 "Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
1289 annotations::SETTINGS_UNIT_LENGTH,
1290 annotations::SETTINGS_UNIT_ANGLE
1291 ),
1292 vec![annotation.as_source_range()],
1293 )));
1294 }
1295 }
1296 }
1297
1298 Ok((updated_len, updated_angle))
1299 }
1300}
1301
1302#[cfg(test)]
1303mod tests {
1304 use uuid::Uuid;
1305
1306 use super::ModuleArtifactState;
1307 use crate::NodePath;
1308 use crate::NodePathExt;
1309 use crate::SourceRange;
1310 use crate::execution::ArtifactId;
1311 use crate::front::Object;
1312 use crate::front::ObjectId;
1313 use crate::front::ObjectKind;
1314 use crate::front::Plane;
1315 use crate::front::SourceRef;
1316
1317 #[test]
1318 fn restore_scene_objects_rebuilds_lookup_maps() {
1319 let plane_artifact_id = ArtifactId::new(Uuid::from_u128(1));
1320 let sketch_artifact_id = ArtifactId::new(Uuid::from_u128(2));
1321 let plane_range = SourceRange::from([1, 4, 0]);
1322 let plane_node_path = Some(NodePath::placeholder());
1323 let sketch_ranges = vec![
1324 (SourceRange::from([5, 9, 0]), None),
1325 (SourceRange::from([10, 12, 0]), None),
1326 ];
1327 let cached_objects = vec![
1328 Object {
1329 id: ObjectId(0),
1330 kind: ObjectKind::Plane(Plane::Object(ObjectId(0))),
1331 label: Default::default(),
1332 comments: Default::default(),
1333 artifact_id: plane_artifact_id,
1334 source: SourceRef::new(plane_range, plane_node_path),
1335 },
1336 Object {
1337 id: ObjectId(1),
1338 kind: ObjectKind::Nil,
1339 label: Default::default(),
1340 comments: Default::default(),
1341 artifact_id: sketch_artifact_id,
1342 source: SourceRef::BackTrace {
1343 ranges: sketch_ranges.clone(),
1344 },
1345 },
1346 Object::placeholder(ObjectId(2), SourceRange::from([13, 14, 0]), None),
1347 ];
1348
1349 let mut artifacts = ModuleArtifactState::default();
1350 artifacts.restore_scene_objects(&cached_objects);
1351
1352 assert_eq!(artifacts.scene_objects, cached_objects);
1353 assert_eq!(
1354 artifacts.artifact_id_to_scene_object.get(&plane_artifact_id),
1355 Some(&ObjectId(0))
1356 );
1357 assert_eq!(
1358 artifacts.artifact_id_to_scene_object.get(&sketch_artifact_id),
1359 Some(&ObjectId(1))
1360 );
1361 assert_eq!(
1362 artifacts.artifact_id_to_scene_object.get(&ArtifactId::placeholder()),
1363 None
1364 );
1365 assert_eq!(artifacts.source_range_to_object.get(&plane_range), Some(&ObjectId(0)));
1366 assert_eq!(
1367 artifacts.source_range_to_object.get(&sketch_ranges[0].0),
1368 Some(&ObjectId(1))
1369 );
1370 assert_eq!(artifacts.source_range_to_object.get(&sketch_ranges[1].0), None);
1372 }
1373}