1#[cfg(feature = "artifact-graph")]
2use std::collections::BTreeMap;
3use std::str::FromStr;
4use std::sync::Arc;
5
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;
19#[cfg(feature = "artifact-graph")]
20use crate::NodePath;
21use crate::SourceRange;
22use crate::collections::AhashIndexSet;
23use crate::errors::KclError;
24use crate::errors::KclErrorDetails;
25use crate::errors::Severity;
26use crate::exec::DefaultPlanes;
27#[cfg(feature = "artifact-graph")]
28use crate::execution::Artifact;
29#[cfg(feature = "artifact-graph")]
30use crate::execution::ArtifactCommand;
31#[cfg(feature = "artifact-graph")]
32use crate::execution::ArtifactGraph;
33#[cfg(feature = "artifact-graph")]
34use crate::execution::ArtifactId;
35use crate::execution::EnvironmentRef;
36use crate::execution::ExecOutcome;
37use crate::execution::ExecutorSettings;
38use crate::execution::KclValue;
39#[cfg(feature = "artifact-graph")]
40use crate::execution::ProgramLookup;
41use crate::execution::SketchVarId;
42use crate::execution::UnsolvedSegment;
43use crate::execution::annotations;
44use crate::execution::cad_op::Operation;
45use crate::execution::id_generator::IdGenerator;
46use crate::execution::memory::ProgramMemory;
47use crate::execution::memory::Stack;
48#[cfg(feature = "artifact-graph")]
49use crate::execution::sketch_solve::Solved;
50use crate::execution::types::NumericType;
51#[cfg(feature = "artifact-graph")]
52use crate::front::Number;
53use crate::front::Object;
54use crate::front::ObjectId;
55use crate::id::IncIdGenerator;
56use crate::modules::ModuleId;
57use crate::modules::ModuleInfo;
58use crate::modules::ModuleLoader;
59use crate::modules::ModulePath;
60use crate::modules::ModuleRepr;
61use crate::modules::ModuleSource;
62use crate::parsing::ast::types::Annotation;
63use crate::parsing::ast::types::NodeRef;
64use crate::parsing::ast::types::TagNode;
65
66#[derive(Debug, Clone)]
68pub struct ExecState {
69 pub(super) global: GlobalState,
70 pub(super) mod_local: ModuleState,
71}
72
73pub type ModuleInfoMap = IndexMap<ModuleId, ModuleInfo>;
74
75#[derive(Debug, Clone)]
76pub(super) struct GlobalState {
77 pub path_to_source_id: IndexMap<ModulePath, ModuleId>,
79 pub id_to_source: IndexMap<ModuleId, ModuleSource>,
81 pub module_infos: ModuleInfoMap,
83 pub mod_loader: ModuleLoader,
85 pub issues: Vec<CompilationIssue>,
87 pub artifacts: ArtifactState,
89 pub root_module_artifacts: ModuleArtifactState,
91 #[cfg(feature = "artifact-graph")]
93 pub segment_ids_edited: AhashIndexSet<ObjectId>,
94}
95
96#[cfg(feature = "artifact-graph")]
97#[derive(Debug, Clone, Default)]
98pub(super) struct ArtifactState {
99 pub artifacts: IndexMap<ArtifactId, Artifact>,
102 pub graph: ArtifactGraph,
104}
105
106#[cfg(not(feature = "artifact-graph"))]
107#[derive(Debug, Clone, Default)]
108pub(super) struct ArtifactState {}
109
110#[cfg(feature = "artifact-graph")]
112#[derive(Debug, Clone, Default, PartialEq, Serialize)]
113pub struct ModuleArtifactState {
114 pub artifacts: IndexMap<ArtifactId, Artifact>,
116 #[serde(skip)]
119 pub unprocessed_commands: Vec<ArtifactCommand>,
120 pub commands: Vec<ArtifactCommand>,
122 pub operations: Vec<Operation>,
125 pub object_id_generator: IncIdGenerator<usize>,
127 pub scene_objects: Vec<Object>,
129 pub source_range_to_object: BTreeMap<SourceRange, ObjectId>,
132 pub artifact_id_to_scene_object: IndexMap<ArtifactId, ObjectId>,
134 pub var_solutions: Vec<(SourceRange, Number)>,
136}
137
138#[cfg(not(feature = "artifact-graph"))]
139#[derive(Debug, Clone, Default, PartialEq, Serialize)]
140pub struct ModuleArtifactState {
141 pub object_id_generator: IncIdGenerator<usize>,
143}
144
145#[derive(Debug, Clone)]
146pub(super) struct ModuleState {
147 pub id_generator: IdGenerator,
149 pub stack: Stack,
150 pub(super) call_stack_size: usize,
154 pub pipe_value: Option<KclValue>,
157 pub being_declared: Option<String>,
161 pub sketch_block: Option<SketchBlockState>,
163 pub inside_stdlib: bool,
166 pub stdlib_entry_source_range: Option<SourceRange>,
168 pub module_exports: Vec<String>,
170 pub settings: MetaSettings,
172 pub sketch_mode: bool,
175 pub freedom_analysis: bool,
179 pub(super) explicit_length_units: bool,
180 pub(super) path: ModulePath,
181 pub artifacts: ModuleArtifactState,
183
184 pub(super) allowed_warnings: Vec<&'static str>,
185 pub(super) denied_warnings: Vec<&'static str>,
186}
187
188#[derive(Debug, Clone, Default)]
189pub(crate) struct SketchBlockState {
190 pub sketch_vars: Vec<KclValue>,
191 #[cfg(feature = "artifact-graph")]
192 pub sketch_id: Option<ObjectId>,
193 #[cfg(feature = "artifact-graph")]
194 pub sketch_constraints: Vec<ObjectId>,
195 pub solver_constraints: Vec<ezpz::Constraint>,
196 pub solver_optional_constraints: Vec<ezpz::Constraint>,
197 pub needed_by_engine: Vec<UnsolvedSegment>,
198 pub segment_tags: IndexMap<ObjectId, TagNode>,
199}
200
201impl ExecState {
202 pub fn new(exec_context: &super::ExecutorContext) -> Self {
203 ExecState {
204 global: GlobalState::new(&exec_context.settings, Default::default()),
205 mod_local: ModuleState::new(ModulePath::Main, ProgramMemory::new(), Default::default(), false, true),
206 }
207 }
208
209 pub fn new_mock(exec_context: &super::ExecutorContext, mock_config: &MockConfig) -> Self {
210 #[cfg(feature = "artifact-graph")]
211 let segment_ids_edited = mock_config.segment_ids_edited.clone();
212 #[cfg(not(feature = "artifact-graph"))]
213 let segment_ids_edited = Default::default();
214 ExecState {
215 global: GlobalState::new(&exec_context.settings, segment_ids_edited),
216 mod_local: ModuleState::new(
217 ModulePath::Main,
218 ProgramMemory::new(),
219 Default::default(),
220 mock_config.sketch_block_id.is_some(),
221 mock_config.freedom_analysis,
222 ),
223 }
224 }
225
226 pub(super) fn reset(&mut self, exec_context: &super::ExecutorContext) {
227 let global = GlobalState::new(&exec_context.settings, Default::default());
228
229 *self = ExecState {
230 global,
231 mod_local: ModuleState::new(
232 self.mod_local.path.clone(),
233 ProgramMemory::new(),
234 Default::default(),
235 false,
236 true,
237 ),
238 };
239 }
240
241 pub fn err(&mut self, e: CompilationIssue) {
243 self.global.issues.push(e);
244 }
245
246 pub fn warn(&mut self, mut e: CompilationIssue, name: &'static str) {
248 debug_assert!(annotations::WARN_VALUES.contains(&name));
249
250 if self.mod_local.allowed_warnings.contains(&name) {
251 return;
252 }
253
254 if self.mod_local.denied_warnings.contains(&name) {
255 e.severity = Severity::Error;
256 } else {
257 e.severity = Severity::Warning;
258 }
259
260 self.global.issues.push(e);
261 }
262
263 pub fn warn_experimental(&mut self, feature_name: &str, source_range: SourceRange) {
264 let Some(severity) = self.mod_local.settings.experimental_features.severity() else {
265 return;
266 };
267 let error = CompilationIssue {
268 source_range,
269 message: format!("Use of {feature_name} is experimental and may change or be removed."),
270 suggestion: None,
271 severity,
272 tag: crate::errors::Tag::None,
273 };
274
275 self.global.issues.push(error);
276 }
277
278 pub fn clear_units_warnings(&mut self, source_range: &SourceRange) {
279 self.global.issues = std::mem::take(&mut self.global.issues)
280 .into_iter()
281 .filter(|e| {
282 e.severity != Severity::Warning
283 || !source_range.contains_range(&e.source_range)
284 || e.tag != crate::errors::Tag::UnknownNumericUnits
285 })
286 .collect();
287 }
288
289 pub fn issues(&self) -> &[CompilationIssue] {
290 &self.global.issues
291 }
292
293 pub async fn into_exec_outcome(self, main_ref: EnvironmentRef, ctx: &ExecutorContext) -> ExecOutcome {
297 ExecOutcome {
300 variables: self.mod_local.variables(main_ref),
301 filenames: self.global.filenames(),
302 #[cfg(feature = "artifact-graph")]
303 operations: self.global.root_module_artifacts.operations,
304 #[cfg(feature = "artifact-graph")]
305 artifact_graph: self.global.artifacts.graph,
306 #[cfg(feature = "artifact-graph")]
307 scene_objects: self.global.root_module_artifacts.scene_objects,
308 #[cfg(feature = "artifact-graph")]
309 source_range_to_object: self.global.root_module_artifacts.source_range_to_object,
310 #[cfg(feature = "artifact-graph")]
311 var_solutions: self.global.root_module_artifacts.var_solutions,
312 issues: self.global.issues,
313 default_planes: ctx.engine.get_default_planes().read().await.clone(),
314 }
315 }
316
317 pub(crate) fn stack(&self) -> &Stack {
318 &self.mod_local.stack
319 }
320
321 pub(crate) fn mut_stack(&mut self) -> &mut Stack {
322 &mut self.mod_local.stack
323 }
324
325 pub(super) fn inc_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
328 if self.mod_local.call_stack_size >= 50 {
331 return Err(KclError::MaxCallStack {
332 details: KclErrorDetails::new("maximum call stack size exceeded".to_owned(), vec![range]),
333 });
334 }
335 self.mod_local.call_stack_size += 1;
336 Ok(())
337 }
338
339 pub(super) fn dec_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
342 if self.mod_local.call_stack_size == 0 {
344 let message = "call stack size below zero".to_owned();
345 debug_assert!(false, "{message}");
346 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
347 }
348 self.mod_local.call_stack_size -= 1;
349 Ok(())
350 }
351
352 pub(crate) fn sketch_mode(&self) -> bool {
357 self.mod_local.sketch_mode
358 && match &self.mod_local.path {
359 ModulePath::Main => true,
360 ModulePath::Local { .. } => true,
361 ModulePath::Std { .. } => false,
362 }
363 }
364
365 pub fn next_object_id(&mut self) -> ObjectId {
366 ObjectId(self.mod_local.artifacts.object_id_generator.next_id())
367 }
368
369 pub fn peek_object_id(&self) -> ObjectId {
370 ObjectId(self.mod_local.artifacts.object_id_generator.peek_id())
371 }
372
373 #[cfg(feature = "artifact-graph")]
374 pub fn add_scene_object(&mut self, obj: Object, source_range: SourceRange) -> ObjectId {
375 let id = obj.id;
376 debug_assert!(
377 id.0 == self.mod_local.artifacts.scene_objects.len(),
378 "Adding scene object with ID {} but next ID is {}",
379 id.0,
380 self.mod_local.artifacts.scene_objects.len()
381 );
382 let artifact_id = obj.artifact_id;
383 self.mod_local.artifacts.scene_objects.push(obj);
384 self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
385 self.mod_local
386 .artifacts
387 .artifact_id_to_scene_object
388 .insert(artifact_id, id);
389 id
390 }
391
392 #[cfg(feature = "artifact-graph")]
395 pub fn add_placeholder_scene_object(
396 &mut self,
397 id: ObjectId,
398 source_range: SourceRange,
399 node_path: Option<NodePath>,
400 ) -> ObjectId {
401 debug_assert!(id.0 == self.mod_local.artifacts.scene_objects.len());
402 self.mod_local
403 .artifacts
404 .scene_objects
405 .push(Object::placeholder(id, source_range, node_path));
406 self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
407 id
408 }
409
410 #[cfg(feature = "artifact-graph")]
412 pub fn set_scene_object(&mut self, object: Object) {
413 let id = object.id;
414 let artifact_id = object.artifact_id;
415 self.mod_local.artifacts.scene_objects[id.0] = object;
416 self.mod_local
417 .artifacts
418 .artifact_id_to_scene_object
419 .insert(artifact_id, id);
420 }
421
422 #[cfg(feature = "artifact-graph")]
423 pub fn scene_object_id_by_artifact_id(&self, artifact_id: ArtifactId) -> Option<ObjectId> {
424 self.mod_local
425 .artifacts
426 .artifact_id_to_scene_object
427 .get(&artifact_id)
428 .cloned()
429 }
430
431 #[cfg(feature = "artifact-graph")]
432 pub fn segment_ids_edited_contains(&self, object_id: &ObjectId) -> bool {
433 self.global.segment_ids_edited.contains(object_id)
434 }
435
436 pub(super) fn is_in_sketch_block(&self) -> bool {
437 self.mod_local.sketch_block.is_some()
438 }
439
440 pub(crate) fn sketch_block_mut(&mut self) -> Option<&mut SketchBlockState> {
441 self.mod_local.sketch_block.as_mut()
442 }
443
444 pub fn next_uuid(&mut self) -> Uuid {
445 self.mod_local.id_generator.next_uuid()
446 }
447
448 #[cfg(feature = "artifact-graph")]
449 pub fn next_artifact_id(&mut self) -> ArtifactId {
450 self.mod_local.id_generator.next_artifact_id()
451 }
452
453 pub fn id_generator(&mut self) -> &mut IdGenerator {
454 &mut self.mod_local.id_generator
455 }
456
457 #[cfg(feature = "artifact-graph")]
458 pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
459 let id = artifact.id();
460 self.mod_local.artifacts.artifacts.insert(id, artifact);
461 }
462
463 #[cfg(feature = "artifact-graph")]
464 pub(crate) fn artifact_mut(&mut self, id: ArtifactId) -> Option<&mut Artifact> {
465 self.mod_local.artifacts.artifacts.get_mut(&id)
466 }
467
468 pub(crate) fn push_op(&mut self, op: Operation) {
469 #[cfg(feature = "artifact-graph")]
470 self.mod_local.artifacts.operations.push(op);
471 #[cfg(not(feature = "artifact-graph"))]
472 drop(op);
473 }
474
475 #[cfg(feature = "artifact-graph")]
476 pub(crate) fn push_command(&mut self, command: ArtifactCommand) {
477 self.mod_local.artifacts.unprocessed_commands.push(command);
478 #[cfg(not(feature = "artifact-graph"))]
479 drop(command);
480 }
481
482 pub(super) fn next_module_id(&self) -> ModuleId {
483 ModuleId::from_usize(self.global.path_to_source_id.len())
484 }
485
486 pub(super) fn id_for_module(&self, path: &ModulePath) -> Option<ModuleId> {
487 self.global.path_to_source_id.get(path).cloned()
488 }
489
490 pub(super) fn add_path_to_source_id(&mut self, path: ModulePath, id: ModuleId) {
491 debug_assert!(!self.global.path_to_source_id.contains_key(&path));
492 self.global.path_to_source_id.insert(path, id);
493 }
494
495 pub(crate) fn add_root_module_contents(&mut self, program: &crate::Program) {
496 let root_id = ModuleId::default();
497 let path = self
499 .global
500 .path_to_source_id
501 .iter()
502 .find(|(_, v)| **v == root_id)
503 .unwrap()
504 .0
505 .clone();
506 self.add_id_to_source(
507 root_id,
508 ModuleSource {
509 path,
510 source: program.original_file_contents.to_string(),
511 },
512 );
513 }
514
515 pub(super) fn add_id_to_source(&mut self, id: ModuleId, source: ModuleSource) {
516 self.global.id_to_source.insert(id, source);
517 }
518
519 pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
520 debug_assert!(self.global.path_to_source_id.contains_key(&path));
521 let module_info = ModuleInfo { id, repr, path };
522 self.global.module_infos.insert(id, module_info);
523 }
524
525 pub fn get_module(&mut self, id: ModuleId) -> Option<&ModuleInfo> {
526 self.global.module_infos.get(&id)
527 }
528
529 #[cfg(all(test, feature = "artifact-graph"))]
530 pub(crate) fn modules(&self) -> &ModuleInfoMap {
531 &self.global.module_infos
532 }
533
534 #[cfg(all(test, feature = "artifact-graph"))]
535 pub(crate) fn root_module_artifact_state(&self) -> &ModuleArtifactState {
536 &self.global.root_module_artifacts
537 }
538
539 pub fn current_default_units(&self) -> NumericType {
540 NumericType::Default {
541 len: self.length_unit(),
542 angle: self.angle_unit(),
543 }
544 }
545
546 pub fn length_unit(&self) -> UnitLength {
547 self.mod_local.settings.default_length_units
548 }
549
550 pub fn angle_unit(&self) -> UnitAngle {
551 self.mod_local.settings.default_angle_units
552 }
553
554 pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
555 KclError::new_import_cycle(KclErrorDetails::new(
556 format!(
557 "circular import of modules is not allowed: {} -> {}",
558 self.global
559 .mod_loader
560 .import_stack
561 .iter()
562 .map(|p| p.to_string_lossy())
563 .collect::<Vec<_>>()
564 .join(" -> "),
565 path,
566 ),
567 vec![source_range],
568 ))
569 }
570
571 pub(crate) fn pipe_value(&self) -> Option<&KclValue> {
572 self.mod_local.pipe_value.as_ref()
573 }
574
575 pub(crate) fn error_with_outputs(
576 &self,
577 error: KclError,
578 main_ref: Option<EnvironmentRef>,
579 default_planes: Option<DefaultPlanes>,
580 ) -> KclErrorWithOutputs {
581 let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = self
582 .global
583 .path_to_source_id
584 .iter()
585 .map(|(k, v)| ((*v), k.clone()))
586 .collect();
587
588 KclErrorWithOutputs::new(
589 error,
590 self.issues().to_vec(),
591 main_ref
592 .map(|main_ref| self.mod_local.variables(main_ref))
593 .unwrap_or_default(),
594 #[cfg(feature = "artifact-graph")]
595 self.global.root_module_artifacts.operations.clone(),
596 #[cfg(feature = "artifact-graph")]
597 Default::default(),
598 #[cfg(feature = "artifact-graph")]
599 self.global.artifacts.graph.clone(),
600 #[cfg(feature = "artifact-graph")]
601 self.global.root_module_artifacts.scene_objects.clone(),
602 #[cfg(feature = "artifact-graph")]
603 self.global.root_module_artifacts.source_range_to_object.clone(),
604 #[cfg(feature = "artifact-graph")]
605 self.global.root_module_artifacts.var_solutions.clone(),
606 module_id_to_module_path,
607 self.global.id_to_source.clone(),
608 default_planes,
609 )
610 }
611
612 #[cfg(feature = "artifact-graph")]
613 pub(crate) fn build_program_lookup(
614 &self,
615 current: crate::parsing::ast::types::Node<crate::parsing::ast::types::Program>,
616 ) -> ProgramLookup {
617 ProgramLookup::new(current, self.global.module_infos.clone())
618 }
619
620 #[cfg(feature = "artifact-graph")]
621 pub(crate) async fn build_artifact_graph(
622 &mut self,
623 engine: &Arc<Box<dyn EngineManager>>,
624 program: NodeRef<'_, crate::parsing::ast::types::Program>,
625 ) -> Result<(), KclError> {
626 let mut new_commands = Vec::new();
627 let mut new_exec_artifacts = IndexMap::new();
628 for module in self.global.module_infos.values_mut() {
629 match &mut module.repr {
630 ModuleRepr::Kcl(_, Some(outcome)) => {
631 new_commands.extend(outcome.artifacts.process_commands());
632 new_exec_artifacts.extend(outcome.artifacts.artifacts.clone());
633 }
634 ModuleRepr::Foreign(_, Some((_, module_artifacts))) => {
635 new_commands.extend(module_artifacts.process_commands());
636 new_exec_artifacts.extend(module_artifacts.artifacts.clone());
637 }
638 ModuleRepr::Root | ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
639 }
640 }
641 new_commands.extend(self.global.root_module_artifacts.process_commands());
644 new_exec_artifacts.extend(self.global.root_module_artifacts.artifacts.clone());
647 let new_responses = engine.take_responses().await;
648
649 for (id, exec_artifact) in new_exec_artifacts {
652 self.global.artifacts.artifacts.entry(id).or_insert(exec_artifact);
656 }
657
658 let initial_graph = self.global.artifacts.graph.clone();
659
660 let programs = self.build_program_lookup(program.clone());
662 let graph_result = crate::execution::artifact::build_artifact_graph(
663 &new_commands,
664 &new_responses,
665 program,
666 &mut self.global.artifacts.artifacts,
667 initial_graph,
668 &programs,
669 &self.global.module_infos,
670 );
671
672 let artifact_graph = graph_result?;
673 self.global.artifacts.graph = artifact_graph;
674
675 Ok(())
676 }
677
678 #[cfg(not(feature = "artifact-graph"))]
679 pub(crate) async fn build_artifact_graph(
680 &mut self,
681 _engine: &Arc<Box<dyn EngineManager>>,
682 _program: NodeRef<'_, crate::parsing::ast::types::Program>,
683 ) -> Result<(), KclError> {
684 Ok(())
685 }
686}
687
688impl GlobalState {
689 fn new(settings: &ExecutorSettings, segment_ids_edited: AhashIndexSet<ObjectId>) -> Self {
690 #[cfg(not(feature = "artifact-graph"))]
691 drop(segment_ids_edited);
692 let mut global = GlobalState {
693 path_to_source_id: Default::default(),
694 module_infos: Default::default(),
695 artifacts: Default::default(),
696 root_module_artifacts: Default::default(),
697 mod_loader: Default::default(),
698 issues: Default::default(),
699 id_to_source: Default::default(),
700 #[cfg(feature = "artifact-graph")]
701 segment_ids_edited,
702 };
703
704 let root_id = ModuleId::default();
705 let root_path = settings.current_file.clone().unwrap_or_default();
706 global.module_infos.insert(
707 root_id,
708 ModuleInfo {
709 id: root_id,
710 path: ModulePath::Local {
711 value: root_path.clone(),
712 original_import_path: None,
713 },
714 repr: ModuleRepr::Root,
715 },
716 );
717 global.path_to_source_id.insert(
718 ModulePath::Local {
719 value: root_path,
720 original_import_path: None,
721 },
722 root_id,
723 );
724 global
725 }
726
727 pub(super) fn filenames(&self) -> IndexMap<ModuleId, ModulePath> {
728 self.path_to_source_id.iter().map(|(k, v)| ((*v), k.clone())).collect()
729 }
730
731 pub(super) fn get_source(&self, id: ModuleId) -> Option<&ModuleSource> {
732 self.id_to_source.get(&id)
733 }
734}
735
736impl ArtifactState {
737 #[cfg(feature = "artifact-graph")]
738 pub fn cached_body_items(&self) -> usize {
739 self.graph.item_count
740 }
741
742 pub(crate) fn clear(&mut self) {
743 #[cfg(feature = "artifact-graph")]
744 {
745 self.artifacts.clear();
746 self.graph.clear();
747 }
748 }
749}
750
751impl ModuleArtifactState {
752 pub(crate) fn clear(&mut self) {
753 #[cfg(feature = "artifact-graph")]
754 {
755 self.artifacts.clear();
756 self.unprocessed_commands.clear();
757 self.commands.clear();
758 self.operations.clear();
759 }
760 }
761
762 #[cfg(feature = "artifact-graph")]
763 pub(crate) fn restore_scene_objects(&mut self, scene_objects: &[Object]) {
764 self.scene_objects = scene_objects.to_vec();
765 self.object_id_generator = IncIdGenerator::new(self.scene_objects.len());
766 self.source_range_to_object.clear();
767 self.artifact_id_to_scene_object.clear();
768
769 for (expected_id, object) in self.scene_objects.iter().enumerate() {
770 debug_assert_eq!(
771 object.id.0, expected_id,
772 "Restored cached scene object ID {} does not match its position {}",
773 object.id.0, expected_id
774 );
775
776 match &object.source {
777 crate::front::SourceRef::Simple { range, node_path: _ } => {
778 self.source_range_to_object.insert(*range, object.id);
779 }
780 crate::front::SourceRef::BackTrace { ranges } => {
781 if let Some((range, _)) = ranges.first() {
784 self.source_range_to_object.insert(*range, object.id);
785 }
786 }
787 }
788
789 if object.artifact_id != ArtifactId::placeholder() {
791 self.artifact_id_to_scene_object.insert(object.artifact_id, object.id);
792 }
793 }
794 }
795
796 #[cfg(not(feature = "artifact-graph"))]
797 pub(crate) fn extend(&mut self, _other: ModuleArtifactState) {}
798
799 #[cfg(feature = "artifact-graph")]
801 pub(crate) fn extend(&mut self, other: ModuleArtifactState) {
802 self.artifacts.extend(other.artifacts);
803 self.unprocessed_commands.extend(other.unprocessed_commands);
804 self.commands.extend(other.commands);
805 self.operations.extend(other.operations);
806 if other.scene_objects.len() > self.scene_objects.len() {
807 self.scene_objects
808 .extend(other.scene_objects[self.scene_objects.len()..].iter().cloned());
809 }
810 self.source_range_to_object.extend(other.source_range_to_object);
811 self.artifact_id_to_scene_object
812 .extend(other.artifact_id_to_scene_object);
813 self.var_solutions.extend(other.var_solutions);
814 }
815
816 #[cfg(feature = "artifact-graph")]
820 pub(crate) fn process_commands(&mut self) -> Vec<ArtifactCommand> {
821 let unprocessed = std::mem::take(&mut self.unprocessed_commands);
822 let new_module_commands = unprocessed.clone();
823 self.commands.extend(unprocessed);
824 new_module_commands
825 }
826
827 #[cfg_attr(not(feature = "artifact-graph"), expect(dead_code))]
828 pub(crate) fn scene_object_by_id(&self, id: ObjectId) -> Option<&Object> {
829 #[cfg(feature = "artifact-graph")]
830 {
831 debug_assert!(
832 id.0 < self.scene_objects.len(),
833 "Requested object ID {} but only have {} objects",
834 id.0,
835 self.scene_objects.len()
836 );
837 self.scene_objects.get(id.0)
838 }
839 #[cfg(not(feature = "artifact-graph"))]
840 {
841 let _ = id;
842 None
843 }
844 }
845
846 #[cfg_attr(not(feature = "artifact-graph"), expect(dead_code))]
847 pub(crate) fn scene_object_by_id_mut(&mut self, id: ObjectId) -> Option<&mut Object> {
848 #[cfg(feature = "artifact-graph")]
849 {
850 debug_assert!(
851 id.0 < self.scene_objects.len(),
852 "Requested object ID {} but only have {} objects",
853 id.0,
854 self.scene_objects.len()
855 );
856 self.scene_objects.get_mut(id.0)
857 }
858 #[cfg(not(feature = "artifact-graph"))]
859 {
860 let _ = id;
861 None
862 }
863 }
864}
865
866impl ModuleState {
867 pub(super) fn new(
868 path: ModulePath,
869 memory: Arc<ProgramMemory>,
870 module_id: Option<ModuleId>,
871 sketch_mode: bool,
872 freedom_analysis: bool,
873 ) -> Self {
874 ModuleState {
875 id_generator: IdGenerator::new(module_id),
876 stack: memory.new_stack(),
877 call_stack_size: 0,
878 pipe_value: Default::default(),
879 being_declared: Default::default(),
880 sketch_block: Default::default(),
881 stdlib_entry_source_range: Default::default(),
882 module_exports: Default::default(),
883 explicit_length_units: false,
884 path,
885 settings: Default::default(),
886 sketch_mode,
887 freedom_analysis,
888 artifacts: Default::default(),
889 allowed_warnings: Vec::new(),
890 denied_warnings: Vec::new(),
891 inside_stdlib: false,
892 }
893 }
894
895 pub(super) fn variables(&self, main_ref: EnvironmentRef) -> IndexMap<String, KclValue> {
896 self.stack
897 .find_all_in_env(main_ref)
898 .map(|(k, v)| (k.clone(), v.clone()))
899 .collect()
900 }
901}
902
903impl SketchBlockState {
904 pub(crate) fn next_sketch_var_id(&self) -> SketchVarId {
905 SketchVarId(self.sketch_vars.len())
906 }
907
908 #[cfg(feature = "artifact-graph")]
911 pub(crate) fn var_solutions(
912 &self,
913 solve_outcome: &Solved,
914 solution_ty: NumericType,
915 range: SourceRange,
916 ) -> Result<Vec<(SourceRange, Number)>, KclError> {
917 self.sketch_vars
918 .iter()
919 .map(|v| {
920 let Some(sketch_var) = v.as_sketch_var() else {
921 return Err(KclError::new_internal(KclErrorDetails::new(
922 "Expected sketch variable".to_owned(),
923 vec![range],
924 )));
925 };
926 let var_index = sketch_var.id.0;
927 let solved_n = solve_outcome.final_values.get(var_index).ok_or_else(|| {
928 let message = format!("No solution for sketch variable with id {}", var_index);
929 debug_assert!(false, "{}", &message);
930 KclError::new_internal(KclErrorDetails::new(
931 message,
932 sketch_var.meta.iter().map(|m| m.source_range).collect(),
933 ))
934 })?;
935 let solved_value = Number {
936 value: *solved_n,
937 units: solution_ty.try_into().map_err(|_| {
938 KclError::new_internal(KclErrorDetails::new(
939 "Failed to convert numeric type to units".to_owned(),
940 vec![range],
941 ))
942 })?,
943 };
944 let Some(source_range) = sketch_var.meta.first().map(|m| m.source_range) else {
945 return Ok(None);
946 };
947 Ok(Some((source_range, solved_value)))
948 })
949 .filter_map(Result::transpose)
950 .collect::<Result<Vec<_>, KclError>>()
951 }
952}
953
954#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
955#[ts(export)]
956#[serde(rename_all = "camelCase")]
957pub struct MetaSettings {
958 pub default_length_units: UnitLength,
959 pub default_angle_units: UnitAngle,
960 pub experimental_features: annotations::WarningLevel,
961 pub kcl_version: String,
962}
963
964impl Default for MetaSettings {
965 fn default() -> Self {
966 MetaSettings {
967 default_length_units: UnitLength::Millimeters,
968 default_angle_units: UnitAngle::Degrees,
969 experimental_features: annotations::WarningLevel::Deny,
970 kcl_version: "1.0".to_owned(),
971 }
972 }
973}
974
975impl MetaSettings {
976 pub(crate) fn update_from_annotation(
977 &mut self,
978 annotation: &crate::parsing::ast::types::Node<Annotation>,
979 ) -> Result<(bool, bool), KclError> {
980 let properties = annotations::expect_properties(annotations::SETTINGS, annotation)?;
981
982 let mut updated_len = false;
983 let mut updated_angle = false;
984 for p in properties {
985 match &*p.inner.key.name {
986 annotations::SETTINGS_UNIT_LENGTH => {
987 let value = annotations::expect_ident(&p.inner.value)?;
988 let value = super::types::length_from_str(value, annotation.as_source_range())?;
989 self.default_length_units = value;
990 updated_len = true;
991 }
992 annotations::SETTINGS_UNIT_ANGLE => {
993 let value = annotations::expect_ident(&p.inner.value)?;
994 let value = super::types::angle_from_str(value, annotation.as_source_range())?;
995 self.default_angle_units = value;
996 updated_angle = true;
997 }
998 annotations::SETTINGS_VERSION => {
999 let value = annotations::expect_number(&p.inner.value)?;
1000 self.kcl_version = value;
1001 }
1002 annotations::SETTINGS_EXPERIMENTAL_FEATURES => {
1003 let value = annotations::expect_ident(&p.inner.value)?;
1004 let value = annotations::WarningLevel::from_str(value).map_err(|_| {
1005 KclError::new_semantic(KclErrorDetails::new(
1006 format!(
1007 "Invalid value for {} settings property, expected one of: {}",
1008 annotations::SETTINGS_EXPERIMENTAL_FEATURES,
1009 annotations::WARN_LEVELS.join(", ")
1010 ),
1011 annotation.as_source_ranges(),
1012 ))
1013 })?;
1014 self.experimental_features = value;
1015 }
1016 name => {
1017 return Err(KclError::new_semantic(KclErrorDetails::new(
1018 format!(
1019 "Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
1020 annotations::SETTINGS_UNIT_LENGTH,
1021 annotations::SETTINGS_UNIT_ANGLE
1022 ),
1023 vec![annotation.as_source_range()],
1024 )));
1025 }
1026 }
1027 }
1028
1029 Ok((updated_len, updated_angle))
1030 }
1031}
1032
1033#[cfg(all(feature = "artifact-graph", test))]
1034mod tests {
1035 use uuid::Uuid;
1036
1037 use super::ModuleArtifactState;
1038 use crate::NodePath;
1039 use crate::SourceRange;
1040 use crate::execution::ArtifactId;
1041 use crate::front::Object;
1042 use crate::front::ObjectId;
1043 use crate::front::ObjectKind;
1044 use crate::front::Plane;
1045 use crate::front::SourceRef;
1046
1047 #[test]
1048 fn restore_scene_objects_rebuilds_lookup_maps() {
1049 let plane_artifact_id = ArtifactId::new(Uuid::from_u128(1));
1050 let sketch_artifact_id = ArtifactId::new(Uuid::from_u128(2));
1051 let plane_range = SourceRange::from([1, 4, 0]);
1052 let plane_node_path = Some(NodePath::placeholder());
1053 let sketch_ranges = vec![
1054 (SourceRange::from([5, 9, 0]), None),
1055 (SourceRange::from([10, 12, 0]), None),
1056 ];
1057 let cached_objects = vec![
1058 Object {
1059 id: ObjectId(0),
1060 kind: ObjectKind::Plane(Plane::Object(ObjectId(0))),
1061 label: Default::default(),
1062 comments: Default::default(),
1063 artifact_id: plane_artifact_id,
1064 source: SourceRef::new(plane_range, plane_node_path),
1065 },
1066 Object {
1067 id: ObjectId(1),
1068 kind: ObjectKind::Nil,
1069 label: Default::default(),
1070 comments: Default::default(),
1071 artifact_id: sketch_artifact_id,
1072 source: SourceRef::BackTrace {
1073 ranges: sketch_ranges.clone(),
1074 },
1075 },
1076 Object::placeholder(ObjectId(2), SourceRange::from([13, 14, 0]), None),
1077 ];
1078
1079 let mut artifacts = ModuleArtifactState::default();
1080 artifacts.restore_scene_objects(&cached_objects);
1081
1082 assert_eq!(artifacts.scene_objects, cached_objects);
1083 assert_eq!(
1084 artifacts.artifact_id_to_scene_object.get(&plane_artifact_id),
1085 Some(&ObjectId(0))
1086 );
1087 assert_eq!(
1088 artifacts.artifact_id_to_scene_object.get(&sketch_artifact_id),
1089 Some(&ObjectId(1))
1090 );
1091 assert_eq!(
1092 artifacts.artifact_id_to_scene_object.get(&ArtifactId::placeholder()),
1093 None
1094 );
1095 assert_eq!(artifacts.source_range_to_object.get(&plane_range), Some(&ObjectId(0)));
1096 assert_eq!(
1097 artifacts.source_range_to_object.get(&sketch_ranges[0].0),
1098 Some(&ObjectId(1))
1099 );
1100 assert_eq!(artifacts.source_range_to_object.get(&sketch_ranges[1].0), None);
1102 }
1103}