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