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