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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
97pub(crate) enum ConstraintKey {
98 LineCircle([usize; 10]),
99 CircleCircle([usize; 12]),
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub(crate) enum TangencyMode {
104 LineCircle(ezpz::LineSide),
105 CircleCircle(ezpz::CircleSide),
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub(crate) enum ConstraintState {
110 Tangency(TangencyMode),
111}
112
113#[cfg(feature = "artifact-graph")]
114#[derive(Debug, Clone, Default)]
115pub(super) struct ArtifactState {
116 pub artifacts: IndexMap<ArtifactId, Artifact>,
119 pub graph: ArtifactGraph,
121}
122
123#[cfg(not(feature = "artifact-graph"))]
124#[derive(Debug, Clone, Default)]
125pub(super) struct ArtifactState {}
126
127#[cfg(feature = "artifact-graph")]
129#[derive(Debug, Clone, Default, PartialEq, Serialize)]
130pub struct ModuleArtifactState {
131 pub artifacts: IndexMap<ArtifactId, Artifact>,
133 #[serde(skip)]
136 pub unprocessed_commands: Vec<ArtifactCommand>,
137 pub commands: Vec<ArtifactCommand>,
139 #[cfg(feature = "snapshot-engine-responses")]
141 pub responses: IndexMap<Uuid, kittycad_modeling_cmds::websocket::WebSocketResponse>,
142 pub operations: Vec<Operation>,
145 pub object_id_generator: IncIdGenerator<usize>,
147 pub scene_objects: Vec<Object>,
149 pub source_range_to_object: BTreeMap<SourceRange, ObjectId>,
152 pub artifact_id_to_scene_object: IndexMap<ArtifactId, ObjectId>,
154 pub var_solutions: Vec<(SourceRange, Number)>,
156}
157
158#[cfg(not(feature = "artifact-graph"))]
159#[derive(Debug, Clone, Default, PartialEq, Serialize)]
160pub struct ModuleArtifactState {
161 pub object_id_generator: IncIdGenerator<usize>,
163}
164
165#[derive(Debug, Clone)]
166pub(super) struct ModuleState {
167 pub id_generator: IdGenerator,
169 pub stack: Stack,
170 pub(super) call_stack_size: usize,
174 pub pipe_value: Option<KclValue>,
177 pub being_declared: Option<String>,
181 pub sketch_block: Option<SketchBlockState>,
183 pub inside_stdlib: bool,
186 pub stdlib_entry_source_range: Option<SourceRange>,
188 pub module_exports: Vec<String>,
190 pub settings: MetaSettings,
192 pub sketch_mode: bool,
195 pub freedom_analysis: bool,
199 pub(super) explicit_length_units: bool,
200 pub(super) path: ModulePath,
201 pub artifacts: ModuleArtifactState,
203 pub constraint_state: IndexMap<ObjectId, IndexMap<ConstraintKey, ConstraintState>>,
207
208 pub(super) allowed_warnings: Vec<&'static str>,
209 pub(super) denied_warnings: Vec<&'static str>,
210}
211
212#[derive(Debug, Clone, Default)]
213pub(crate) struct SketchBlockState {
214 pub sketch_vars: Vec<KclValue>,
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 pub(crate) fn constraint_state(&self, sketch_block_id: ObjectId, key: &ConstraintKey) -> Option<ConstraintState> {
404 let map = self.mod_local.constraint_state.get(&sketch_block_id)?;
405 map.get(key).copied()
406 }
407
408 pub(crate) fn set_constraint_state(
409 &mut self,
410 sketch_block_id: ObjectId,
411 key: ConstraintKey,
412 state: ConstraintState,
413 ) {
414 let map = self.mod_local.constraint_state.entry(sketch_block_id).or_default();
415 map.insert(key, state);
416 }
417
418 #[cfg(feature = "artifact-graph")]
419 pub fn add_scene_object(&mut self, obj: Object, source_range: SourceRange) -> ObjectId {
420 let id = obj.id;
421 debug_assert!(
422 id.0 == self.mod_local.artifacts.scene_objects.len(),
423 "Adding scene object with ID {} but next ID is {}",
424 id.0,
425 self.mod_local.artifacts.scene_objects.len()
426 );
427 let artifact_id = obj.artifact_id;
428 self.mod_local.artifacts.scene_objects.push(obj);
429 self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
430 self.mod_local
431 .artifacts
432 .artifact_id_to_scene_object
433 .insert(artifact_id, id);
434 id
435 }
436
437 #[cfg(feature = "artifact-graph")]
440 pub fn add_placeholder_scene_object(
441 &mut self,
442 id: ObjectId,
443 source_range: SourceRange,
444 node_path: Option<NodePath>,
445 ) -> ObjectId {
446 debug_assert!(id.0 == self.mod_local.artifacts.scene_objects.len());
447 self.mod_local
448 .artifacts
449 .scene_objects
450 .push(Object::placeholder(id, source_range, node_path));
451 self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
452 id
453 }
454
455 #[cfg(feature = "artifact-graph")]
457 pub fn set_scene_object(&mut self, object: Object) {
458 let id = object.id;
459 let artifact_id = object.artifact_id;
460 self.mod_local.artifacts.scene_objects[id.0] = object;
461 self.mod_local
462 .artifacts
463 .artifact_id_to_scene_object
464 .insert(artifact_id, id);
465 }
466
467 #[cfg(feature = "artifact-graph")]
468 pub fn scene_object_id_by_artifact_id(&self, artifact_id: ArtifactId) -> Option<ObjectId> {
469 self.mod_local
470 .artifacts
471 .artifact_id_to_scene_object
472 .get(&artifact_id)
473 .cloned()
474 }
475
476 #[cfg(feature = "artifact-graph")]
477 pub fn segment_ids_edited_contains(&self, object_id: &ObjectId) -> bool {
478 self.global.segment_ids_edited.contains(object_id)
479 }
480
481 pub(super) fn is_in_sketch_block(&self) -> bool {
482 self.mod_local.sketch_block.is_some()
483 }
484
485 pub(crate) fn sketch_block_mut(&mut self) -> Option<&mut SketchBlockState> {
486 self.mod_local.sketch_block.as_mut()
487 }
488
489 pub(crate) fn sketch_block(&mut self) -> Option<&SketchBlockState> {
490 self.mod_local.sketch_block.as_ref()
491 }
492
493 pub fn next_uuid(&mut self) -> Uuid {
494 self.mod_local.id_generator.next_uuid()
495 }
496
497 #[cfg(feature = "artifact-graph")]
498 pub fn next_artifact_id(&mut self) -> ArtifactId {
499 self.mod_local.id_generator.next_artifact_id()
500 }
501
502 pub fn id_generator(&mut self) -> &mut IdGenerator {
503 &mut self.mod_local.id_generator
504 }
505
506 #[cfg(feature = "artifact-graph")]
507 pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
508 let id = artifact.id();
509 self.mod_local.artifacts.artifacts.insert(id, artifact);
510 }
511
512 #[cfg(feature = "artifact-graph")]
513 pub(crate) fn artifact_mut(&mut self, id: ArtifactId) -> Option<&mut Artifact> {
514 self.mod_local.artifacts.artifacts.get_mut(&id)
515 }
516
517 pub(crate) fn push_op(&mut self, op: Operation) {
518 #[cfg(feature = "artifact-graph")]
519 self.mod_local.artifacts.operations.push(op);
520 #[cfg(not(feature = "artifact-graph"))]
521 drop(op);
522 }
523
524 #[cfg(feature = "artifact-graph")]
525 pub(crate) fn push_command(&mut self, command: ArtifactCommand) {
526 self.mod_local.artifacts.unprocessed_commands.push(command);
527 #[cfg(not(feature = "artifact-graph"))]
528 drop(command);
529 }
530
531 pub(super) fn next_module_id(&self) -> ModuleId {
532 ModuleId::from_usize(self.global.path_to_source_id.len())
533 }
534
535 pub(super) fn id_for_module(&self, path: &ModulePath) -> Option<ModuleId> {
536 self.global.path_to_source_id.get(path).cloned()
537 }
538
539 pub(super) fn add_path_to_source_id(&mut self, path: ModulePath, id: ModuleId) {
540 debug_assert!(!self.global.path_to_source_id.contains_key(&path));
541 self.global.path_to_source_id.insert(path, id);
542 }
543
544 pub(crate) fn add_root_module_contents(&mut self, program: &crate::Program) {
545 let root_id = ModuleId::default();
546 let path = self
548 .global
549 .path_to_source_id
550 .iter()
551 .find(|(_, v)| **v == root_id)
552 .unwrap()
553 .0
554 .clone();
555 self.add_id_to_source(
556 root_id,
557 ModuleSource {
558 path,
559 source: program.original_file_contents.to_string(),
560 },
561 );
562 }
563
564 pub(super) fn add_id_to_source(&mut self, id: ModuleId, source: ModuleSource) {
565 self.global.id_to_source.insert(id, source);
566 }
567
568 pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
569 debug_assert!(self.global.path_to_source_id.contains_key(&path));
570 let module_info = ModuleInfo { id, repr, path };
571 self.global.module_infos.insert(id, module_info);
572 }
573
574 pub fn get_module(&mut self, id: ModuleId) -> Option<&ModuleInfo> {
575 self.global.module_infos.get(&id)
576 }
577
578 #[cfg(all(test, feature = "artifact-graph"))]
579 pub(crate) fn modules(&self) -> &ModuleInfoMap {
580 &self.global.module_infos
581 }
582
583 #[cfg(all(test, feature = "artifact-graph"))]
584 pub(crate) fn root_module_artifact_state(&self) -> &ModuleArtifactState {
585 &self.global.root_module_artifacts
586 }
587
588 pub fn current_default_units(&self) -> NumericType {
589 NumericType::Default {
590 len: self.length_unit(),
591 angle: self.angle_unit(),
592 }
593 }
594
595 pub fn length_unit(&self) -> UnitLength {
596 self.mod_local.settings.default_length_units
597 }
598
599 pub fn angle_unit(&self) -> UnitAngle {
600 self.mod_local.settings.default_angle_units
601 }
602
603 pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
604 KclError::new_import_cycle(KclErrorDetails::new(
605 format!(
606 "circular import of modules is not allowed: {} -> {}",
607 self.global
608 .mod_loader
609 .import_stack
610 .iter()
611 .map(|p| p.to_string_lossy())
612 .collect::<Vec<_>>()
613 .join(" -> "),
614 path,
615 ),
616 vec![source_range],
617 ))
618 }
619
620 pub(crate) fn pipe_value(&self) -> Option<&KclValue> {
621 self.mod_local.pipe_value.as_ref()
622 }
623
624 pub(crate) fn error_with_outputs(
625 &self,
626 error: KclError,
627 main_ref: Option<EnvironmentRef>,
628 default_planes: Option<DefaultPlanes>,
629 ) -> KclErrorWithOutputs {
630 let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = self
631 .global
632 .path_to_source_id
633 .iter()
634 .map(|(k, v)| ((*v), k.clone()))
635 .collect();
636
637 KclErrorWithOutputs::new(
638 error,
639 self.issues().to_vec(),
640 main_ref
641 .map(|main_ref| self.mod_local.variables(main_ref))
642 .unwrap_or_default(),
643 #[cfg(feature = "artifact-graph")]
644 self.global.root_module_artifacts.operations.clone(),
645 #[cfg(feature = "artifact-graph")]
646 Default::default(),
647 #[cfg(feature = "artifact-graph")]
648 self.global.artifacts.graph.clone(),
649 #[cfg(feature = "artifact-graph")]
650 self.global.root_module_artifacts.scene_objects.clone(),
651 #[cfg(feature = "artifact-graph")]
652 self.global.root_module_artifacts.source_range_to_object.clone(),
653 #[cfg(feature = "artifact-graph")]
654 self.global.root_module_artifacts.var_solutions.clone(),
655 module_id_to_module_path,
656 self.global.id_to_source.clone(),
657 default_planes,
658 )
659 }
660
661 #[cfg(feature = "artifact-graph")]
662 pub(crate) fn build_program_lookup(
663 &self,
664 current: crate::parsing::ast::types::Node<crate::parsing::ast::types::Program>,
665 ) -> ProgramLookup {
666 ProgramLookup::new(current, self.global.module_infos.clone())
667 }
668
669 #[cfg(feature = "artifact-graph")]
670 pub(crate) async fn build_artifact_graph(
671 &mut self,
672 engine: &Arc<Box<dyn EngineManager>>,
673 program: NodeRef<'_, crate::parsing::ast::types::Program>,
674 ) -> Result<(), KclError> {
675 let mut new_commands = Vec::new();
676 let mut new_exec_artifacts = IndexMap::new();
677 for module in self.global.module_infos.values_mut() {
678 match &mut module.repr {
679 ModuleRepr::Kcl(_, Some(outcome)) => {
680 new_commands.extend(outcome.artifacts.process_commands());
681 new_exec_artifacts.extend(outcome.artifacts.artifacts.clone());
682 }
683 ModuleRepr::Foreign(_, Some((_, module_artifacts))) => {
684 new_commands.extend(module_artifacts.process_commands());
685 new_exec_artifacts.extend(module_artifacts.artifacts.clone());
686 }
687 ModuleRepr::Root | ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
688 }
689 }
690 new_commands.extend(self.global.root_module_artifacts.process_commands());
693 new_exec_artifacts.extend(self.global.root_module_artifacts.artifacts.clone());
696 let new_responses = engine.take_responses().await;
697
698 for (id, exec_artifact) in new_exec_artifacts {
701 self.global.artifacts.artifacts.entry(id).or_insert(exec_artifact);
705 }
706
707 let initial_graph = self.global.artifacts.graph.clone();
708
709 let programs = self.build_program_lookup(program.clone());
711 let graph_result = crate::execution::artifact::build_artifact_graph(
712 &new_commands,
713 &new_responses,
714 program,
715 &mut self.global.artifacts.artifacts,
716 initial_graph,
717 &programs,
718 &self.global.module_infos,
719 );
720
721 #[cfg(feature = "snapshot-engine-responses")]
722 {
723 self.global.root_module_artifacts.responses.extend(new_responses);
725 }
726
727 let artifact_graph = graph_result?;
728 self.global.artifacts.graph = artifact_graph;
729
730 Ok(())
731 }
732
733 #[cfg(not(feature = "artifact-graph"))]
734 pub(crate) async fn build_artifact_graph(
735 &mut self,
736 _engine: &Arc<Box<dyn EngineManager>>,
737 _program: NodeRef<'_, crate::parsing::ast::types::Program>,
738 ) -> Result<(), KclError> {
739 Ok(())
740 }
741}
742
743impl GlobalState {
744 fn new(settings: &ExecutorSettings, segment_ids_edited: AhashIndexSet<ObjectId>) -> Self {
745 #[cfg(not(feature = "artifact-graph"))]
746 drop(segment_ids_edited);
747 let mut global = GlobalState {
748 path_to_source_id: Default::default(),
749 module_infos: Default::default(),
750 artifacts: Default::default(),
751 root_module_artifacts: Default::default(),
752 mod_loader: Default::default(),
753 issues: Default::default(),
754 id_to_source: Default::default(),
755 #[cfg(feature = "artifact-graph")]
756 segment_ids_edited,
757 };
758
759 let root_id = ModuleId::default();
760 let root_path = settings.current_file.clone().unwrap_or_default();
761 global.module_infos.insert(
762 root_id,
763 ModuleInfo {
764 id: root_id,
765 path: ModulePath::Local {
766 value: root_path.clone(),
767 original_import_path: None,
768 },
769 repr: ModuleRepr::Root,
770 },
771 );
772 global.path_to_source_id.insert(
773 ModulePath::Local {
774 value: root_path,
775 original_import_path: None,
776 },
777 root_id,
778 );
779 global
780 }
781
782 pub(super) fn filenames(&self) -> IndexMap<ModuleId, ModulePath> {
783 self.path_to_source_id.iter().map(|(k, v)| ((*v), k.clone())).collect()
784 }
785
786 pub(super) fn get_source(&self, id: ModuleId) -> Option<&ModuleSource> {
787 self.id_to_source.get(&id)
788 }
789}
790
791impl ArtifactState {
792 #[cfg(feature = "artifact-graph")]
793 pub fn cached_body_items(&self) -> usize {
794 self.graph.item_count
795 }
796
797 pub(crate) fn clear(&mut self) {
798 #[cfg(feature = "artifact-graph")]
799 {
800 self.artifacts.clear();
801 self.graph.clear();
802 }
803 }
804}
805
806impl ModuleArtifactState {
807 pub(crate) fn clear(&mut self) {
808 #[cfg(feature = "artifact-graph")]
809 {
810 self.artifacts.clear();
811 self.unprocessed_commands.clear();
812 self.commands.clear();
813 self.operations.clear();
814 }
815 }
816
817 #[cfg(feature = "artifact-graph")]
818 pub(crate) fn restore_scene_objects(&mut self, scene_objects: &[Object]) {
819 self.scene_objects = scene_objects.to_vec();
820 self.object_id_generator = IncIdGenerator::new(self.scene_objects.len());
821 self.source_range_to_object.clear();
822 self.artifact_id_to_scene_object.clear();
823
824 for (expected_id, object) in self.scene_objects.iter().enumerate() {
825 debug_assert_eq!(
826 object.id.0, expected_id,
827 "Restored cached scene object ID {} does not match its position {}",
828 object.id.0, expected_id
829 );
830
831 match &object.source {
832 crate::front::SourceRef::Simple { range, node_path: _ } => {
833 self.source_range_to_object.insert(*range, object.id);
834 }
835 crate::front::SourceRef::BackTrace { ranges } => {
836 if let Some((range, _)) = ranges.first() {
839 self.source_range_to_object.insert(*range, object.id);
840 }
841 }
842 }
843
844 if object.artifact_id != ArtifactId::placeholder() {
846 self.artifact_id_to_scene_object.insert(object.artifact_id, object.id);
847 }
848 }
849 }
850
851 #[cfg(not(feature = "artifact-graph"))]
852 pub(crate) fn extend(&mut self, _other: ModuleArtifactState) {}
853
854 #[cfg(feature = "artifact-graph")]
856 pub(crate) fn extend(&mut self, other: ModuleArtifactState) {
857 self.artifacts.extend(other.artifacts);
858 self.unprocessed_commands.extend(other.unprocessed_commands);
859 self.commands.extend(other.commands);
860 self.operations.extend(other.operations);
861 if other.scene_objects.len() > self.scene_objects.len() {
862 self.scene_objects
863 .extend(other.scene_objects[self.scene_objects.len()..].iter().cloned());
864 }
865 self.source_range_to_object.extend(other.source_range_to_object);
866 self.artifact_id_to_scene_object
867 .extend(other.artifact_id_to_scene_object);
868 self.var_solutions.extend(other.var_solutions);
869 }
870
871 #[cfg(feature = "artifact-graph")]
875 pub(crate) fn process_commands(&mut self) -> Vec<ArtifactCommand> {
876 let unprocessed = std::mem::take(&mut self.unprocessed_commands);
877 let new_module_commands = unprocessed.clone();
878 self.commands.extend(unprocessed);
879 new_module_commands
880 }
881
882 #[cfg_attr(not(feature = "artifact-graph"), expect(dead_code))]
883 pub(crate) fn scene_object_by_id(&self, id: ObjectId) -> Option<&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(id.0)
893 }
894 #[cfg(not(feature = "artifact-graph"))]
895 {
896 let _ = id;
897 None
898 }
899 }
900
901 #[cfg_attr(not(feature = "artifact-graph"), expect(dead_code))]
902 pub(crate) fn scene_object_by_id_mut(&mut self, id: ObjectId) -> Option<&mut Object> {
903 #[cfg(feature = "artifact-graph")]
904 {
905 debug_assert!(
906 id.0 < self.scene_objects.len(),
907 "Requested object ID {} but only have {} objects",
908 id.0,
909 self.scene_objects.len()
910 );
911 self.scene_objects.get_mut(id.0)
912 }
913 #[cfg(not(feature = "artifact-graph"))]
914 {
915 let _ = id;
916 None
917 }
918 }
919}
920
921impl ModuleState {
922 pub(super) fn new(
923 path: ModulePath,
924 memory: Arc<ProgramMemory>,
925 module_id: Option<ModuleId>,
926 sketch_mode: bool,
927 freedom_analysis: bool,
928 ) -> Self {
929 ModuleState {
930 id_generator: IdGenerator::new(module_id),
931 stack: memory.new_stack(),
932 call_stack_size: 0,
933 pipe_value: Default::default(),
934 being_declared: Default::default(),
935 sketch_block: Default::default(),
936 stdlib_entry_source_range: Default::default(),
937 module_exports: Default::default(),
938 explicit_length_units: false,
939 path,
940 settings: Default::default(),
941 sketch_mode,
942 freedom_analysis,
943 artifacts: Default::default(),
944 constraint_state: Default::default(),
945 allowed_warnings: Vec::new(),
946 denied_warnings: Vec::new(),
947 inside_stdlib: false,
948 }
949 }
950
951 pub(super) fn variables(&self, main_ref: EnvironmentRef) -> IndexMap<String, KclValue> {
952 self.stack
953 .find_all_in_env(main_ref)
954 .map(|(k, v)| (k.clone(), v.clone()))
955 .collect()
956 }
957}
958
959impl SketchBlockState {
960 pub(crate) fn next_sketch_var_id(&self) -> SketchVarId {
961 SketchVarId(self.sketch_vars.len())
962 }
963
964 #[cfg(feature = "artifact-graph")]
967 pub(crate) fn var_solutions(
968 &self,
969 solve_outcome: &Solved,
970 solution_ty: NumericType,
971 range: SourceRange,
972 ) -> Result<Vec<(SourceRange, Number)>, KclError> {
973 self.sketch_vars
974 .iter()
975 .map(|v| {
976 let Some(sketch_var) = v.as_sketch_var() else {
977 return Err(KclError::new_internal(KclErrorDetails::new(
978 "Expected sketch variable".to_owned(),
979 vec![range],
980 )));
981 };
982 let var_index = sketch_var.id.0;
983 let solved_n = solve_outcome.final_values.get(var_index).ok_or_else(|| {
984 let message = format!("No solution for sketch variable with id {}", var_index);
985 debug_assert!(false, "{}", &message);
986 KclError::new_internal(KclErrorDetails::new(
987 message,
988 sketch_var.meta.iter().map(|m| m.source_range).collect(),
989 ))
990 })?;
991 let solved_value = Number {
992 value: *solved_n,
993 units: solution_ty.try_into().map_err(|_| {
994 KclError::new_internal(KclErrorDetails::new(
995 "Failed to convert numeric type to units".to_owned(),
996 vec![range],
997 ))
998 })?,
999 };
1000 let Some(source_range) = sketch_var.meta.first().map(|m| m.source_range) else {
1001 return Ok(None);
1002 };
1003 Ok(Some((source_range, solved_value)))
1004 })
1005 .filter_map(Result::transpose)
1006 .collect::<Result<Vec<_>, KclError>>()
1007 }
1008}
1009
1010#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
1011#[ts(export)]
1012#[serde(rename_all = "camelCase")]
1013pub struct MetaSettings {
1014 pub default_length_units: UnitLength,
1015 pub default_angle_units: UnitAngle,
1016 pub experimental_features: annotations::WarningLevel,
1017 pub kcl_version: String,
1018}
1019
1020impl Default for MetaSettings {
1021 fn default() -> Self {
1022 MetaSettings {
1023 default_length_units: UnitLength::Millimeters,
1024 default_angle_units: UnitAngle::Degrees,
1025 experimental_features: annotations::WarningLevel::Deny,
1026 kcl_version: "1.0".to_owned(),
1027 }
1028 }
1029}
1030
1031impl MetaSettings {
1032 pub(crate) fn update_from_annotation(
1033 &mut self,
1034 annotation: &crate::parsing::ast::types::Node<Annotation>,
1035 ) -> Result<(bool, bool), KclError> {
1036 let properties = annotations::expect_properties(annotations::SETTINGS, annotation)?;
1037
1038 let mut updated_len = false;
1039 let mut updated_angle = false;
1040 for p in properties {
1041 match &*p.inner.key.name {
1042 annotations::SETTINGS_UNIT_LENGTH => {
1043 let value = annotations::expect_ident(&p.inner.value)?;
1044 let value = super::types::length_from_str(value, annotation.as_source_range())?;
1045 self.default_length_units = value;
1046 updated_len = true;
1047 }
1048 annotations::SETTINGS_UNIT_ANGLE => {
1049 let value = annotations::expect_ident(&p.inner.value)?;
1050 let value = super::types::angle_from_str(value, annotation.as_source_range())?;
1051 self.default_angle_units = value;
1052 updated_angle = true;
1053 }
1054 annotations::SETTINGS_VERSION => {
1055 let value = annotations::expect_number(&p.inner.value)?;
1056 self.kcl_version = value;
1057 }
1058 annotations::SETTINGS_EXPERIMENTAL_FEATURES => {
1059 let value = annotations::expect_ident(&p.inner.value)?;
1060 let value = annotations::WarningLevel::from_str(value).map_err(|_| {
1061 KclError::new_semantic(KclErrorDetails::new(
1062 format!(
1063 "Invalid value for {} settings property, expected one of: {}",
1064 annotations::SETTINGS_EXPERIMENTAL_FEATURES,
1065 annotations::WARN_LEVELS.join(", ")
1066 ),
1067 annotation.as_source_ranges(),
1068 ))
1069 })?;
1070 self.experimental_features = value;
1071 }
1072 name => {
1073 return Err(KclError::new_semantic(KclErrorDetails::new(
1074 format!(
1075 "Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
1076 annotations::SETTINGS_UNIT_LENGTH,
1077 annotations::SETTINGS_UNIT_ANGLE
1078 ),
1079 vec![annotation.as_source_range()],
1080 )));
1081 }
1082 }
1083 }
1084
1085 Ok((updated_len, updated_angle))
1086 }
1087}
1088
1089#[cfg(all(feature = "artifact-graph", test))]
1090mod tests {
1091 use uuid::Uuid;
1092
1093 use super::ModuleArtifactState;
1094 use crate::NodePath;
1095 use crate::SourceRange;
1096 use crate::execution::ArtifactId;
1097 use crate::front::Object;
1098 use crate::front::ObjectId;
1099 use crate::front::ObjectKind;
1100 use crate::front::Plane;
1101 use crate::front::SourceRef;
1102
1103 #[test]
1104 fn restore_scene_objects_rebuilds_lookup_maps() {
1105 let plane_artifact_id = ArtifactId::new(Uuid::from_u128(1));
1106 let sketch_artifact_id = ArtifactId::new(Uuid::from_u128(2));
1107 let plane_range = SourceRange::from([1, 4, 0]);
1108 let plane_node_path = Some(NodePath::placeholder());
1109 let sketch_ranges = vec![
1110 (SourceRange::from([5, 9, 0]), None),
1111 (SourceRange::from([10, 12, 0]), None),
1112 ];
1113 let cached_objects = vec![
1114 Object {
1115 id: ObjectId(0),
1116 kind: ObjectKind::Plane(Plane::Object(ObjectId(0))),
1117 label: Default::default(),
1118 comments: Default::default(),
1119 artifact_id: plane_artifact_id,
1120 source: SourceRef::new(plane_range, plane_node_path),
1121 },
1122 Object {
1123 id: ObjectId(1),
1124 kind: ObjectKind::Nil,
1125 label: Default::default(),
1126 comments: Default::default(),
1127 artifact_id: sketch_artifact_id,
1128 source: SourceRef::BackTrace {
1129 ranges: sketch_ranges.clone(),
1130 },
1131 },
1132 Object::placeholder(ObjectId(2), SourceRange::from([13, 14, 0]), None),
1133 ];
1134
1135 let mut artifacts = ModuleArtifactState::default();
1136 artifacts.restore_scene_objects(&cached_objects);
1137
1138 assert_eq!(artifacts.scene_objects, cached_objects);
1139 assert_eq!(
1140 artifacts.artifact_id_to_scene_object.get(&plane_artifact_id),
1141 Some(&ObjectId(0))
1142 );
1143 assert_eq!(
1144 artifacts.artifact_id_to_scene_object.get(&sketch_artifact_id),
1145 Some(&ObjectId(1))
1146 );
1147 assert_eq!(
1148 artifacts.artifact_id_to_scene_object.get(&ArtifactId::placeholder()),
1149 None
1150 );
1151 assert_eq!(artifacts.source_range_to_object.get(&plane_range), Some(&ObjectId(0)));
1152 assert_eq!(
1153 artifacts.source_range_to_object.get(&sketch_ranges[0].0),
1154 Some(&ObjectId(1))
1155 );
1156 assert_eq!(artifacts.source_range_to_object.get(&sketch_ranges[1].0), None);
1158 }
1159}