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