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