Skip to main content

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