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