kcl_lib/execution/
state.rs

1#[cfg(feature = "artifact-graph")]
2use std::collections::BTreeMap;
3use std::{str::FromStr, sync::Arc};
4
5use anyhow::Result;
6use indexmap::IndexMap;
7use kittycad_modeling_cmds::units::{UnitAngle, UnitLength};
8use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10
11use crate::{
12    CompilationError, EngineManager, ExecutorContext, KclErrorWithOutputs, MockConfig, SourceRange,
13    collections::AhashIndexSet,
14    errors::{KclError, KclErrorDetails, Severity},
15    exec::DefaultPlanes,
16    execution::{
17        EnvironmentRef, ExecOutcome, ExecutorSettings, KclValue, SketchVarId, UnsolvedSegment, annotations,
18        cad_op::Operation,
19        id_generator::IdGenerator,
20        memory::{ProgramMemory, Stack},
21        types::NumericType,
22    },
23    front::{Object, ObjectId},
24    modules::{ModuleId, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr, ModuleSource},
25    parsing::ast::types::{Annotation, NodeRef},
26};
27#[cfg(feature = "artifact-graph")]
28use crate::{
29    execution::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId, ProgramLookup, sketch_solve::Solved},
30    front::Number,
31    id::IncIdGenerator,
32};
33
34/// State for executing a program.
35#[derive(Debug, Clone)]
36pub struct ExecState {
37    pub(super) global: GlobalState,
38    pub(super) mod_local: ModuleState,
39}
40
41pub type ModuleInfoMap = IndexMap<ModuleId, ModuleInfo>;
42
43#[derive(Debug, Clone)]
44pub(super) struct GlobalState {
45    /// Map from source file absolute path to module ID.
46    pub path_to_source_id: IndexMap<ModulePath, ModuleId>,
47    /// Map from module ID to source file.
48    pub id_to_source: IndexMap<ModuleId, ModuleSource>,
49    /// Map from module ID to module info.
50    pub module_infos: ModuleInfoMap,
51    /// Module loader.
52    pub mod_loader: ModuleLoader,
53    /// Errors and warnings.
54    pub errors: Vec<CompilationError>,
55    /// Global artifacts that represent the entire program.
56    pub artifacts: ArtifactState,
57    /// Artifacts for only the root module.
58    pub root_module_artifacts: ModuleArtifactState,
59    /// The segments that were edited that triggered this execution.
60    #[cfg(feature = "artifact-graph")]
61    pub segment_ids_edited: AhashIndexSet<ObjectId>,
62}
63
64#[cfg(feature = "artifact-graph")]
65#[derive(Debug, Clone, Default)]
66pub(super) struct ArtifactState {
67    /// Internal map of UUIDs to exec artifacts.  This needs to persist across
68    /// executions to allow the graph building to refer to cached artifacts.
69    pub artifacts: IndexMap<ArtifactId, Artifact>,
70    /// Output artifact graph.
71    pub graph: ArtifactGraph,
72}
73
74#[cfg(not(feature = "artifact-graph"))]
75#[derive(Debug, Clone, Default)]
76pub(super) struct ArtifactState {}
77
78/// Artifact state for a single module.
79#[cfg(feature = "artifact-graph")]
80#[derive(Debug, Clone, Default, PartialEq, Serialize)]
81pub struct ModuleArtifactState {
82    /// Internal map of UUIDs to exec artifacts.
83    pub artifacts: IndexMap<ArtifactId, Artifact>,
84    /// Outgoing engine commands that have not yet been processed and integrated
85    /// into the artifact graph.
86    #[serde(skip)]
87    pub unprocessed_commands: Vec<ArtifactCommand>,
88    /// Outgoing engine commands.
89    pub commands: Vec<ArtifactCommand>,
90    /// Operations that have been performed in execution order, for display in
91    /// the Feature Tree.
92    pub operations: Vec<Operation>,
93    /// [`ObjectId`] generator.
94    pub object_id_generator: IncIdGenerator<usize>,
95    /// Objects in the scene, created from execution.
96    pub scene_objects: Vec<Object>,
97    /// Map from source range to object ID for lookup of objects by their source
98    /// range.
99    pub source_range_to_object: BTreeMap<SourceRange, ObjectId>,
100    /// Solutions for sketch variables.
101    pub var_solutions: Vec<(SourceRange, Number)>,
102}
103
104#[cfg(not(feature = "artifact-graph"))]
105#[derive(Debug, Clone, Default, PartialEq, Serialize)]
106pub struct ModuleArtifactState {}
107
108#[derive(Debug, Clone)]
109pub(super) struct ModuleState {
110    /// The id generator for this module.
111    pub id_generator: IdGenerator,
112    pub stack: Stack,
113    /// The size of the call stack. This is used to prevent stack overflows with
114    /// recursive function calls. In general, this doesn't match `stack`'s size
115    /// since it's conservative in reclaiming frames between executions.
116    pub(super) call_stack_size: usize,
117    /// The current value of the pipe operator returned from the previous
118    /// expression.  If we're not currently in a pipeline, this will be None.
119    pub pipe_value: Option<KclValue>,
120    /// The closest variable declaration being executed in any parent node in the AST.
121    /// This is used to provide better error messages, e.g. noticing when the user is trying
122    /// to use the variable `length` inside the RHS of its own definition, like `length = tan(length)`.
123    pub being_declared: Option<String>,
124    /// Present if we're currently executing inside a sketch block.
125    pub sketch_block: Option<SketchBlockState>,
126    /// Tracks if KCL being executed is currently inside a stdlib function or not.
127    /// This matters because e.g. we shouldn't emit artifacts from declarations declared inside a stdlib function.
128    pub inside_stdlib: bool,
129    /// The source range where we entered the standard library.
130    pub stdlib_entry_source_range: Option<SourceRange>,
131    /// Identifiers that have been exported from the current module.
132    pub module_exports: Vec<String>,
133    /// Settings specified from annotations.
134    pub settings: MetaSettings,
135    /// True if executing in sketch mode. Only a single sketch block will be
136    /// executed. All other code is ignored.
137    pub sketch_mode: bool,
138    /// True to do more costly analysis of whether the sketch block segments are
139    /// under-constrained. The only time we disable this is when a user is
140    /// dragging segments.
141    pub freedom_analysis: bool,
142    pub(super) explicit_length_units: bool,
143    pub(super) path: ModulePath,
144    /// Artifacts for only this module.
145    pub artifacts: ModuleArtifactState,
146
147    pub(super) allowed_warnings: Vec<&'static str>,
148    pub(super) denied_warnings: Vec<&'static str>,
149}
150
151#[derive(Debug, Clone, Default)]
152pub(crate) struct SketchBlockState {
153    pub sketch_vars: Vec<KclValue>,
154    #[cfg(feature = "artifact-graph")]
155    pub sketch_constraints: Vec<ObjectId>,
156    pub solver_constraints: Vec<kcl_ezpz::Constraint>,
157    pub solver_optional_constraints: Vec<kcl_ezpz::Constraint>,
158    pub needed_by_engine: Vec<UnsolvedSegment>,
159}
160
161impl ExecState {
162    pub fn new(exec_context: &super::ExecutorContext) -> Self {
163        ExecState {
164            global: GlobalState::new(&exec_context.settings, Default::default()),
165            mod_local: ModuleState::new(ModulePath::Main, ProgramMemory::new(), Default::default(), false, true),
166        }
167    }
168
169    pub fn new_mock(exec_context: &super::ExecutorContext, mock_config: &MockConfig) -> Self {
170        #[cfg(feature = "artifact-graph")]
171        let segment_ids_edited = mock_config.segment_ids_edited.clone();
172        #[cfg(not(feature = "artifact-graph"))]
173        let segment_ids_edited = Default::default();
174        ExecState {
175            global: GlobalState::new(&exec_context.settings, segment_ids_edited),
176            mod_local: ModuleState::new(
177                ModulePath::Main,
178                ProgramMemory::new(),
179                Default::default(),
180                mock_config.sketch_block_id.is_some(),
181                mock_config.freedom_analysis,
182            ),
183        }
184    }
185
186    pub(super) fn reset(&mut self, exec_context: &super::ExecutorContext) {
187        let global = GlobalState::new(&exec_context.settings, Default::default());
188
189        *self = ExecState {
190            global,
191            mod_local: ModuleState::new(
192                self.mod_local.path.clone(),
193                ProgramMemory::new(),
194                Default::default(),
195                false,
196                true,
197            ),
198        };
199    }
200
201    /// Log a non-fatal error.
202    pub fn err(&mut self, e: CompilationError) {
203        self.global.errors.push(e);
204    }
205
206    /// Log a warning.
207    pub fn warn(&mut self, mut e: CompilationError, name: &'static str) {
208        debug_assert!(annotations::WARN_VALUES.contains(&name));
209
210        if self.mod_local.allowed_warnings.contains(&name) {
211            return;
212        }
213
214        if self.mod_local.denied_warnings.contains(&name) {
215            e.severity = Severity::Error;
216        } else {
217            e.severity = Severity::Warning;
218        }
219
220        self.global.errors.push(e);
221    }
222
223    pub fn warn_experimental(&mut self, feature_name: &str, source_range: SourceRange) {
224        let Some(severity) = self.mod_local.settings.experimental_features.severity() else {
225            return;
226        };
227        let error = CompilationError {
228            source_range,
229            message: format!("Use of {feature_name} is experimental and may change or be removed."),
230            suggestion: None,
231            severity,
232            tag: crate::errors::Tag::None,
233        };
234
235        self.global.errors.push(error);
236    }
237
238    pub fn clear_units_warnings(&mut self, source_range: &SourceRange) {
239        self.global.errors = std::mem::take(&mut self.global.errors)
240            .into_iter()
241            .filter(|e| {
242                e.severity != Severity::Warning
243                    || !source_range.contains_range(&e.source_range)
244                    || e.tag != crate::errors::Tag::UnknownNumericUnits
245            })
246            .collect();
247    }
248
249    pub fn errors(&self) -> &[CompilationError] {
250        &self.global.errors
251    }
252
253    /// Convert to execution outcome when running in WebAssembly.  We want to
254    /// reduce the amount of data that crosses the WASM boundary as much as
255    /// possible.
256    pub async fn into_exec_outcome(self, main_ref: EnvironmentRef, ctx: &ExecutorContext) -> ExecOutcome {
257        // Fields are opt-in so that we don't accidentally leak private internal
258        // state when we add more to ExecState.
259        ExecOutcome {
260            variables: self.mod_local.variables(main_ref),
261            filenames: self.global.filenames(),
262            #[cfg(feature = "artifact-graph")]
263            operations: self.global.root_module_artifacts.operations,
264            #[cfg(feature = "artifact-graph")]
265            artifact_graph: self.global.artifacts.graph,
266            #[cfg(feature = "artifact-graph")]
267            scene_objects: self.global.root_module_artifacts.scene_objects,
268            #[cfg(feature = "artifact-graph")]
269            source_range_to_object: self.global.root_module_artifacts.source_range_to_object,
270            #[cfg(feature = "artifact-graph")]
271            var_solutions: self.global.root_module_artifacts.var_solutions,
272            errors: self.global.errors,
273            default_planes: ctx.engine.get_default_planes().read().await.clone(),
274        }
275    }
276
277    pub(crate) fn stack(&self) -> &Stack {
278        &self.mod_local.stack
279    }
280
281    pub(crate) fn mut_stack(&mut self) -> &mut Stack {
282        &mut self.mod_local.stack
283    }
284
285    /// Increment the user-level call stack size, returning an error if it
286    /// exceeds the maximum.
287    pub(super) fn inc_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
288        // If you change this, make sure to test in WebAssembly in the app since
289        // that's the limiting factor.
290        if self.mod_local.call_stack_size >= 50 {
291            return Err(KclError::MaxCallStack {
292                details: KclErrorDetails::new("maximum call stack size exceeded".to_owned(), vec![range]),
293            });
294        }
295        self.mod_local.call_stack_size += 1;
296        Ok(())
297    }
298
299    /// Decrement the user-level call stack size, returning an error if it would
300    /// go below zero.
301    pub(super) fn dec_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
302        // Prevent underflow.
303        if self.mod_local.call_stack_size == 0 {
304            let message = "call stack size below zero".to_owned();
305            debug_assert!(false, "{message}");
306            return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
307        }
308        self.mod_local.call_stack_size -= 1;
309        Ok(())
310    }
311
312    /// Returns true if we're executing in sketch mode for the current module.
313    /// In sketch mode, we still want to execute the prelude and other stdlib
314    /// modules as normal, so it can vary per module within a single overall
315    /// execution.
316    pub(crate) fn sketch_mode(&self) -> bool {
317        self.mod_local.sketch_mode
318            && match &self.mod_local.path {
319                ModulePath::Main => true,
320                ModulePath::Local { .. } => true,
321                ModulePath::Std { .. } => false,
322            }
323    }
324
325    #[cfg(not(feature = "artifact-graph"))]
326    pub fn next_object_id(&mut self) -> ObjectId {
327        // The return value should only ever be used when the feature is
328        // enabled,
329        ObjectId(0)
330    }
331
332    #[cfg(feature = "artifact-graph")]
333    pub fn next_object_id(&mut self) -> ObjectId {
334        ObjectId(self.mod_local.artifacts.object_id_generator.next_id())
335    }
336
337    #[cfg(not(feature = "artifact-graph"))]
338    pub fn peek_object_id(&self) -> ObjectId {
339        // The return value should only ever be used when the feature is
340        // enabled,
341        ObjectId(0)
342    }
343
344    #[cfg(feature = "artifact-graph")]
345    pub fn peek_object_id(&self) -> ObjectId {
346        ObjectId(self.mod_local.artifacts.object_id_generator.peek_id())
347    }
348
349    #[cfg(feature = "artifact-graph")]
350    pub fn add_scene_object(&mut self, obj: Object, source_range: SourceRange) -> ObjectId {
351        let id = obj.id;
352        debug_assert!(
353            id.0 == self.mod_local.artifacts.scene_objects.len(),
354            "Adding scene object with ID {} but next ID is {}",
355            id.0,
356            self.mod_local.artifacts.scene_objects.len()
357        );
358        self.mod_local.artifacts.scene_objects.push(obj);
359        self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
360        id
361    }
362
363    /// Add a placeholder scene object. This is useful when we need to reserve
364    /// an ID before we have all the information to create the full object.
365    #[cfg(feature = "artifact-graph")]
366    pub fn add_placeholder_scene_object(&mut self, id: ObjectId, source_range: SourceRange) -> ObjectId {
367        debug_assert!(id.0 == self.mod_local.artifacts.scene_objects.len());
368        self.mod_local
369            .artifacts
370            .scene_objects
371            .push(Object::placeholder(id, source_range));
372        self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
373        id
374    }
375
376    /// Update a scene object. This is useful to replace a placeholder.
377    #[cfg(feature = "artifact-graph")]
378    pub fn set_scene_object(&mut self, object: Object) {
379        let id = object.id;
380        self.mod_local.artifacts.scene_objects[id.0] = object;
381    }
382
383    #[cfg(feature = "artifact-graph")]
384    pub fn segment_ids_edited_contains(&self, object_id: &ObjectId) -> bool {
385        self.global.segment_ids_edited.contains(object_id)
386    }
387
388    pub(super) fn is_in_sketch_block(&self) -> bool {
389        self.mod_local.sketch_block.is_some()
390    }
391
392    pub(crate) fn sketch_block_mut(&mut self) -> Option<&mut SketchBlockState> {
393        self.mod_local.sketch_block.as_mut()
394    }
395
396    pub fn next_uuid(&mut self) -> Uuid {
397        self.mod_local.id_generator.next_uuid()
398    }
399
400    #[cfg(feature = "artifact-graph")]
401    pub fn next_artifact_id(&mut self) -> ArtifactId {
402        self.mod_local.id_generator.next_artifact_id()
403    }
404
405    pub fn id_generator(&mut self) -> &mut IdGenerator {
406        &mut self.mod_local.id_generator
407    }
408
409    #[cfg(feature = "artifact-graph")]
410    pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
411        let id = artifact.id();
412        self.mod_local.artifacts.artifacts.insert(id, artifact);
413    }
414
415    pub(crate) fn push_op(&mut self, op: Operation) {
416        #[cfg(feature = "artifact-graph")]
417        self.mod_local.artifacts.operations.push(op);
418        #[cfg(not(feature = "artifact-graph"))]
419        drop(op);
420    }
421
422    #[cfg(feature = "artifact-graph")]
423    pub(crate) fn push_command(&mut self, command: ArtifactCommand) {
424        self.mod_local.artifacts.unprocessed_commands.push(command);
425        #[cfg(not(feature = "artifact-graph"))]
426        drop(command);
427    }
428
429    pub(super) fn next_module_id(&self) -> ModuleId {
430        ModuleId::from_usize(self.global.path_to_source_id.len())
431    }
432
433    pub(super) fn id_for_module(&self, path: &ModulePath) -> Option<ModuleId> {
434        self.global.path_to_source_id.get(path).cloned()
435    }
436
437    pub(super) fn add_path_to_source_id(&mut self, path: ModulePath, id: ModuleId) {
438        debug_assert!(!self.global.path_to_source_id.contains_key(&path));
439        self.global.path_to_source_id.insert(path, id);
440    }
441
442    pub(crate) fn add_root_module_contents(&mut self, program: &crate::Program) {
443        let root_id = ModuleId::default();
444        // Get the path for the root module.
445        let path = self
446            .global
447            .path_to_source_id
448            .iter()
449            .find(|(_, v)| **v == root_id)
450            .unwrap()
451            .0
452            .clone();
453        self.add_id_to_source(
454            root_id,
455            ModuleSource {
456                path,
457                source: program.original_file_contents.to_string(),
458            },
459        );
460    }
461
462    pub(super) fn add_id_to_source(&mut self, id: ModuleId, source: ModuleSource) {
463        self.global.id_to_source.insert(id, source);
464    }
465
466    pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
467        debug_assert!(self.global.path_to_source_id.contains_key(&path));
468        let module_info = ModuleInfo { id, repr, path };
469        self.global.module_infos.insert(id, module_info);
470    }
471
472    pub fn get_module(&mut self, id: ModuleId) -> Option<&ModuleInfo> {
473        self.global.module_infos.get(&id)
474    }
475
476    #[cfg(all(test, feature = "artifact-graph"))]
477    pub(crate) fn modules(&self) -> &ModuleInfoMap {
478        &self.global.module_infos
479    }
480
481    #[cfg(all(test, feature = "artifact-graph"))]
482    pub(crate) fn root_module_artifact_state(&self) -> &ModuleArtifactState {
483        &self.global.root_module_artifacts
484    }
485
486    pub fn current_default_units(&self) -> NumericType {
487        NumericType::Default {
488            len: self.length_unit(),
489            angle: self.angle_unit(),
490        }
491    }
492
493    pub fn length_unit(&self) -> UnitLength {
494        self.mod_local.settings.default_length_units
495    }
496
497    pub fn angle_unit(&self) -> UnitAngle {
498        self.mod_local.settings.default_angle_units
499    }
500
501    pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
502        KclError::new_import_cycle(KclErrorDetails::new(
503            format!(
504                "circular import of modules is not allowed: {} -> {}",
505                self.global
506                    .mod_loader
507                    .import_stack
508                    .iter()
509                    .map(|p| p.to_string_lossy())
510                    .collect::<Vec<_>>()
511                    .join(" -> "),
512                path,
513            ),
514            vec![source_range],
515        ))
516    }
517
518    pub(crate) fn pipe_value(&self) -> Option<&KclValue> {
519        self.mod_local.pipe_value.as_ref()
520    }
521
522    pub(crate) fn error_with_outputs(
523        &self,
524        error: KclError,
525        main_ref: Option<EnvironmentRef>,
526        default_planes: Option<DefaultPlanes>,
527    ) -> KclErrorWithOutputs {
528        let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = self
529            .global
530            .path_to_source_id
531            .iter()
532            .map(|(k, v)| ((*v), k.clone()))
533            .collect();
534
535        KclErrorWithOutputs::new(
536            error,
537            self.errors().to_vec(),
538            main_ref
539                .map(|main_ref| self.mod_local.variables(main_ref))
540                .unwrap_or_default(),
541            #[cfg(feature = "artifact-graph")]
542            self.global.root_module_artifacts.operations.clone(),
543            #[cfg(feature = "artifact-graph")]
544            Default::default(),
545            #[cfg(feature = "artifact-graph")]
546            self.global.artifacts.graph.clone(),
547            module_id_to_module_path,
548            self.global.id_to_source.clone(),
549            default_planes,
550        )
551    }
552
553    #[cfg(feature = "artifact-graph")]
554    pub(crate) fn build_program_lookup(
555        &self,
556        current: crate::parsing::ast::types::Node<crate::parsing::ast::types::Program>,
557    ) -> ProgramLookup {
558        ProgramLookup::new(current, self.global.module_infos.clone())
559    }
560
561    #[cfg(feature = "artifact-graph")]
562    pub(crate) async fn build_artifact_graph(
563        &mut self,
564        engine: &Arc<Box<dyn EngineManager>>,
565        program: NodeRef<'_, crate::parsing::ast::types::Program>,
566    ) -> Result<(), KclError> {
567        let mut new_commands = Vec::new();
568        let mut new_exec_artifacts = IndexMap::new();
569        for module in self.global.module_infos.values_mut() {
570            match &mut module.repr {
571                ModuleRepr::Kcl(_, Some(outcome)) => {
572                    new_commands.extend(outcome.artifacts.process_commands());
573                    new_exec_artifacts.extend(outcome.artifacts.artifacts.clone());
574                }
575                ModuleRepr::Foreign(_, Some((_, module_artifacts))) => {
576                    new_commands.extend(module_artifacts.process_commands());
577                    new_exec_artifacts.extend(module_artifacts.artifacts.clone());
578                }
579                ModuleRepr::Root | ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
580            }
581        }
582        // Take from the module artifacts so that we don't try to process them
583        // again next time due to execution caching.
584        new_commands.extend(self.global.root_module_artifacts.process_commands());
585        // Note: These will get re-processed, but since we're just adding them
586        // to a map, it's fine.
587        new_exec_artifacts.extend(self.global.root_module_artifacts.artifacts.clone());
588        let new_responses = engine.take_responses().await;
589
590        // Move the artifacts into ExecState global to simplify cache
591        // management.
592        self.global.artifacts.artifacts.extend(new_exec_artifacts);
593
594        let initial_graph = self.global.artifacts.graph.clone();
595
596        // Build the artifact graph.
597        let programs = self.build_program_lookup(program.clone());
598        let graph_result = crate::execution::artifact::build_artifact_graph(
599            &new_commands,
600            &new_responses,
601            program,
602            &mut self.global.artifacts.artifacts,
603            initial_graph,
604            &programs,
605        );
606
607        let artifact_graph = graph_result?;
608        self.global.artifacts.graph = artifact_graph;
609
610        Ok(())
611    }
612
613    #[cfg(not(feature = "artifact-graph"))]
614    pub(crate) async fn build_artifact_graph(
615        &mut self,
616        _engine: &Arc<Box<dyn EngineManager>>,
617        _program: NodeRef<'_, crate::parsing::ast::types::Program>,
618    ) -> Result<(), KclError> {
619        Ok(())
620    }
621}
622
623impl GlobalState {
624    fn new(settings: &ExecutorSettings, segment_ids_edited: AhashIndexSet<ObjectId>) -> Self {
625        #[cfg(not(feature = "artifact-graph"))]
626        drop(segment_ids_edited);
627        let mut global = GlobalState {
628            path_to_source_id: Default::default(),
629            module_infos: Default::default(),
630            artifacts: Default::default(),
631            root_module_artifacts: Default::default(),
632            mod_loader: Default::default(),
633            errors: Default::default(),
634            id_to_source: Default::default(),
635            #[cfg(feature = "artifact-graph")]
636            segment_ids_edited,
637        };
638
639        let root_id = ModuleId::default();
640        let root_path = settings.current_file.clone().unwrap_or_default();
641        global.module_infos.insert(
642            root_id,
643            ModuleInfo {
644                id: root_id,
645                path: ModulePath::Local {
646                    value: root_path.clone(),
647                    original_import_path: None,
648                },
649                repr: ModuleRepr::Root,
650            },
651        );
652        global.path_to_source_id.insert(
653            ModulePath::Local {
654                value: root_path,
655                original_import_path: None,
656            },
657            root_id,
658        );
659        global
660    }
661
662    pub(super) fn filenames(&self) -> IndexMap<ModuleId, ModulePath> {
663        self.path_to_source_id.iter().map(|(k, v)| ((*v), k.clone())).collect()
664    }
665
666    pub(super) fn get_source(&self, id: ModuleId) -> Option<&ModuleSource> {
667        self.id_to_source.get(&id)
668    }
669}
670
671impl ArtifactState {
672    #[cfg(feature = "artifact-graph")]
673    pub fn cached_body_items(&self) -> usize {
674        self.graph.item_count
675    }
676
677    pub(crate) fn clear(&mut self) {
678        #[cfg(feature = "artifact-graph")]
679        {
680            self.artifacts.clear();
681            self.graph.clear();
682        }
683    }
684}
685
686impl ModuleArtifactState {
687    pub(crate) fn clear(&mut self) {
688        #[cfg(feature = "artifact-graph")]
689        {
690            self.artifacts.clear();
691            self.unprocessed_commands.clear();
692            self.commands.clear();
693            self.operations.clear();
694        }
695    }
696
697    #[cfg(not(feature = "artifact-graph"))]
698    pub(crate) fn extend(&mut self, _other: ModuleArtifactState) {}
699
700    /// When self is a cached state, extend it with new state.
701    #[cfg(feature = "artifact-graph")]
702    pub(crate) fn extend(&mut self, other: ModuleArtifactState) {
703        self.artifacts.extend(other.artifacts);
704        self.unprocessed_commands.extend(other.unprocessed_commands);
705        self.commands.extend(other.commands);
706        self.operations.extend(other.operations);
707        if other.scene_objects.len() > self.scene_objects.len() {
708            self.scene_objects
709                .extend(other.scene_objects[self.scene_objects.len()..].iter().cloned());
710        }
711        self.source_range_to_object.extend(other.source_range_to_object);
712        self.var_solutions.extend(other.var_solutions);
713    }
714
715    // Move unprocessed artifact commands so that we don't try to process them
716    // again next time due to execution caching.  Returns a clone of the
717    // commands that were moved.
718    #[cfg(feature = "artifact-graph")]
719    pub(crate) fn process_commands(&mut self) -> Vec<ArtifactCommand> {
720        let unprocessed = std::mem::take(&mut self.unprocessed_commands);
721        let new_module_commands = unprocessed.clone();
722        self.commands.extend(unprocessed);
723        new_module_commands
724    }
725
726    #[cfg_attr(not(feature = "artifact-graph"), expect(dead_code))]
727    pub(crate) fn scene_object_by_id(&self, id: ObjectId) -> Option<&Object> {
728        #[cfg(feature = "artifact-graph")]
729        {
730            debug_assert!(
731                id.0 < self.scene_objects.len(),
732                "Requested object ID {} but only have {} objects",
733                id.0,
734                self.scene_objects.len()
735            );
736            self.scene_objects.get(id.0)
737        }
738        #[cfg(not(feature = "artifact-graph"))]
739        {
740            let _ = id;
741            None
742        }
743    }
744
745    #[cfg_attr(not(feature = "artifact-graph"), expect(dead_code))]
746    pub(crate) fn scene_object_by_id_mut(&mut self, id: ObjectId) -> Option<&mut Object> {
747        #[cfg(feature = "artifact-graph")]
748        {
749            debug_assert!(
750                id.0 < self.scene_objects.len(),
751                "Requested object ID {} but only have {} objects",
752                id.0,
753                self.scene_objects.len()
754            );
755            self.scene_objects.get_mut(id.0)
756        }
757        #[cfg(not(feature = "artifact-graph"))]
758        {
759            let _ = id;
760            None
761        }
762    }
763}
764
765impl ModuleState {
766    pub(super) fn new(
767        path: ModulePath,
768        memory: Arc<ProgramMemory>,
769        module_id: Option<ModuleId>,
770        sketch_mode: bool,
771        freedom_analysis: bool,
772    ) -> Self {
773        ModuleState {
774            id_generator: IdGenerator::new(module_id),
775            stack: memory.new_stack(),
776            call_stack_size: 0,
777            pipe_value: Default::default(),
778            being_declared: Default::default(),
779            sketch_block: Default::default(),
780            stdlib_entry_source_range: Default::default(),
781            module_exports: Default::default(),
782            explicit_length_units: false,
783            path,
784            settings: Default::default(),
785            sketch_mode,
786            freedom_analysis,
787            artifacts: Default::default(),
788            allowed_warnings: Vec::new(),
789            denied_warnings: Vec::new(),
790            inside_stdlib: false,
791        }
792    }
793
794    pub(super) fn variables(&self, main_ref: EnvironmentRef) -> IndexMap<String, KclValue> {
795        self.stack
796            .find_all_in_env(main_ref)
797            .map(|(k, v)| (k.clone(), v.clone()))
798            .collect()
799    }
800}
801
802impl SketchBlockState {
803    pub(crate) fn next_sketch_var_id(&self) -> SketchVarId {
804        SketchVarId(self.sketch_vars.len())
805    }
806
807    /// Given a solve outcome, return the solutions for the sketch variables and
808    /// enough information to update them in the source.
809    #[cfg(feature = "artifact-graph")]
810    pub(crate) fn var_solutions(
811        &self,
812        solve_outcome: Solved,
813        solution_ty: NumericType,
814        range: SourceRange,
815    ) -> Result<Vec<(SourceRange, Number)>, KclError> {
816        self.sketch_vars
817            .iter()
818            .map(|v| {
819                let Some(sketch_var) = v.as_sketch_var() else {
820                    return Err(KclError::new_internal(KclErrorDetails::new(
821                        "Expected sketch variable".to_owned(),
822                        vec![range],
823                    )));
824                };
825                let var_index = sketch_var.id.0;
826                let solved_n = solve_outcome.final_values.get(var_index).ok_or_else(|| {
827                    let message = format!("No solution for sketch variable with id {}", var_index);
828                    debug_assert!(false, "{}", &message);
829                    KclError::new_internal(KclErrorDetails::new(
830                        message,
831                        sketch_var.meta.iter().map(|m| m.source_range).collect(),
832                    ))
833                })?;
834                let solved_value = Number {
835                    value: *solved_n,
836                    units: solution_ty.try_into().map_err(|_| {
837                        KclError::new_internal(KclErrorDetails::new(
838                            "Failed to convert numeric type to units".to_owned(),
839                            vec![range],
840                        ))
841                    })?,
842                };
843                let Some(source_range) = sketch_var.meta.first().map(|m| m.source_range) else {
844                    return Ok(None);
845                };
846                Ok(Some((source_range, solved_value)))
847            })
848            .filter_map(Result::transpose)
849            .collect::<Result<Vec<_>, KclError>>()
850    }
851}
852
853#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
854#[ts(export)]
855#[serde(rename_all = "camelCase")]
856pub struct MetaSettings {
857    pub default_length_units: UnitLength,
858    pub default_angle_units: UnitAngle,
859    pub experimental_features: annotations::WarningLevel,
860    pub kcl_version: String,
861}
862
863impl Default for MetaSettings {
864    fn default() -> Self {
865        MetaSettings {
866            default_length_units: UnitLength::Millimeters,
867            default_angle_units: UnitAngle::Degrees,
868            experimental_features: annotations::WarningLevel::Deny,
869            kcl_version: "1.0".to_owned(),
870        }
871    }
872}
873
874impl MetaSettings {
875    pub(crate) fn update_from_annotation(
876        &mut self,
877        annotation: &crate::parsing::ast::types::Node<Annotation>,
878    ) -> Result<(bool, bool), KclError> {
879        let properties = annotations::expect_properties(annotations::SETTINGS, annotation)?;
880
881        let mut updated_len = false;
882        let mut updated_angle = false;
883        for p in properties {
884            match &*p.inner.key.name {
885                annotations::SETTINGS_UNIT_LENGTH => {
886                    let value = annotations::expect_ident(&p.inner.value)?;
887                    let value = super::types::length_from_str(value, annotation.as_source_range())?;
888                    self.default_length_units = value;
889                    updated_len = true;
890                }
891                annotations::SETTINGS_UNIT_ANGLE => {
892                    let value = annotations::expect_ident(&p.inner.value)?;
893                    let value = super::types::angle_from_str(value, annotation.as_source_range())?;
894                    self.default_angle_units = value;
895                    updated_angle = true;
896                }
897                annotations::SETTINGS_VERSION => {
898                    let value = annotations::expect_number(&p.inner.value)?;
899                    self.kcl_version = value;
900                }
901                annotations::SETTINGS_EXPERIMENTAL_FEATURES => {
902                    let value = annotations::expect_ident(&p.inner.value)?;
903                    let value = annotations::WarningLevel::from_str(value).map_err(|_| {
904                        KclError::new_semantic(KclErrorDetails::new(
905                            format!(
906                                "Invalid value for {} settings property, expected one of: {}",
907                                annotations::SETTINGS_EXPERIMENTAL_FEATURES,
908                                annotations::WARN_LEVELS.join(", ")
909                            ),
910                            annotation.as_source_ranges(),
911                        ))
912                    })?;
913                    self.experimental_features = value;
914                }
915                name => {
916                    return Err(KclError::new_semantic(KclErrorDetails::new(
917                        format!(
918                            "Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
919                            annotations::SETTINGS_UNIT_LENGTH,
920                            annotations::SETTINGS_UNIT_ANGLE
921                        ),
922                        vec![annotation.as_source_range()],
923                    )));
924                }
925            }
926        }
927
928        Ok((updated_len, updated_angle))
929    }
930}