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