kcl_lib/execution/
state.rs

1use std::sync::Arc;
2
3use anyhow::Result;
4use indexmap::IndexMap;
5use kittycad_modeling_cmds::websocket::WebSocketResponse;
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8use uuid::Uuid;
9
10use crate::{
11    errors::{KclError, KclErrorDetails, Severity},
12    execution::{
13        annotations,
14        id_generator::IdGenerator,
15        memory::{ProgramMemory, Stack},
16        types, Artifact, ArtifactCommand, ArtifactGraph, ArtifactId, EnvironmentRef, ExecOutcome, ExecutorSettings,
17        KclValue, Operation, UnitAngle, UnitLen,
18    },
19    modules::{ModuleId, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr, ModuleSource},
20    parsing::ast::types::Annotation,
21    source_range::SourceRange,
22    CompilationError,
23};
24
25/// State for executing a program.
26#[derive(Debug, Clone)]
27pub struct ExecState {
28    pub(super) global: GlobalState,
29    pub(super) mod_local: ModuleState,
30    pub(super) exec_context: Option<super::ExecutorContext>,
31}
32
33#[derive(Debug, Clone)]
34pub(super) struct GlobalState {
35    /// Map from source file absolute path to module ID.
36    pub path_to_source_id: IndexMap<ModulePath, ModuleId>,
37    /// Map from module ID to source file.
38    pub id_to_source: IndexMap<ModuleId, ModuleSource>,
39    /// Map from module ID to module info.
40    pub module_infos: IndexMap<ModuleId, ModuleInfo>,
41    /// Output map of UUIDs to artifacts.
42    pub artifacts: IndexMap<ArtifactId, Artifact>,
43    /// Output commands to allow building the artifact graph by the caller.
44    /// These are accumulated in the [`ExecutorContext`] but moved here for
45    /// convenience of the execution cache.
46    pub artifact_commands: Vec<ArtifactCommand>,
47    /// Responses from the engine for `artifact_commands`.  We need to cache
48    /// this so that we can build the artifact graph.  These are accumulated in
49    /// the [`ExecutorContext`] but moved here for convenience of the execution
50    /// cache.
51    pub artifact_responses: IndexMap<Uuid, WebSocketResponse>,
52    /// Output artifact graph.
53    pub artifact_graph: ArtifactGraph,
54    /// Operations that have been performed in execution order, for display in
55    /// the Feature Tree.
56    pub operations: Vec<Operation>,
57    /// Module loader.
58    pub mod_loader: ModuleLoader,
59    /// Errors and warnings.
60    pub errors: Vec<CompilationError>,
61}
62
63#[derive(Debug, Clone)]
64pub(super) struct ModuleState {
65    /// The id generator for this module.
66    pub id_generator: IdGenerator,
67    pub stack: Stack,
68    /// The current value of the pipe operator returned from the previous
69    /// expression.  If we're not currently in a pipeline, this will be None.
70    pub pipe_value: Option<KclValue>,
71    /// Identifiers that have been exported from the current module.
72    pub module_exports: Vec<String>,
73    /// Settings specified from annotations.
74    pub settings: MetaSettings,
75    pub(super) explicit_length_units: bool,
76    pub(super) std_path: Option<String>,
77}
78
79impl ExecState {
80    pub fn new(exec_context: &super::ExecutorContext) -> Self {
81        ExecState {
82            global: GlobalState::new(&exec_context.settings),
83            mod_local: ModuleState::new(&exec_context.settings, None, ProgramMemory::new(), Default::default()),
84            exec_context: Some(exec_context.clone()),
85        }
86    }
87
88    pub(super) fn reset(&mut self, exec_context: &super::ExecutorContext) {
89        let global = GlobalState::new(&exec_context.settings);
90
91        *self = ExecState {
92            global,
93            mod_local: ModuleState::new(&exec_context.settings, None, ProgramMemory::new(), Default::default()),
94            exec_context: Some(exec_context.clone()),
95        };
96    }
97
98    /// Log a non-fatal error.
99    pub fn err(&mut self, e: CompilationError) {
100        self.global.errors.push(e);
101    }
102
103    /// Log a warning.
104    pub fn warn(&mut self, mut e: CompilationError) {
105        e.severity = Severity::Warning;
106        self.global.errors.push(e);
107    }
108
109    pub fn errors(&self) -> &[CompilationError] {
110        &self.global.errors
111    }
112
113    /// Convert to execution outcome when running in WebAssembly.  We want to
114    /// reduce the amount of data that crosses the WASM boundary as much as
115    /// possible.
116    pub async fn to_wasm_outcome(self, main_ref: EnvironmentRef) -> ExecOutcome {
117        // Fields are opt-in so that we don't accidentally leak private internal
118        // state when we add more to ExecState.
119        ExecOutcome {
120            variables: self
121                .stack()
122                .find_all_in_env(main_ref)
123                .map(|(k, v)| (k.clone(), v.clone()))
124                .collect(),
125            operations: self.global.operations,
126            artifact_commands: self.global.artifact_commands,
127            artifact_graph: self.global.artifact_graph,
128            errors: self.global.errors,
129            filenames: self
130                .global
131                .path_to_source_id
132                .iter()
133                .map(|(k, v)| ((*v), k.clone()))
134                .collect(),
135            default_planes: if let Some(ctx) = &self.exec_context {
136                ctx.engine.get_default_planes().read().await.clone()
137            } else {
138                None
139            },
140        }
141    }
142
143    pub async fn to_mock_wasm_outcome(self, main_ref: EnvironmentRef) -> ExecOutcome {
144        // Fields are opt-in so that we don't accidentally leak private internal
145        // state when we add more to ExecState.
146        ExecOutcome {
147            variables: self
148                .stack()
149                .find_all_in_env(main_ref)
150                .map(|(k, v)| (k.clone(), v.clone()))
151                .collect(),
152            operations: Default::default(),
153            artifact_commands: Default::default(),
154            artifact_graph: Default::default(),
155            errors: self.global.errors,
156            filenames: Default::default(),
157            default_planes: if let Some(ctx) = &self.exec_context {
158                ctx.engine.get_default_planes().read().await.clone()
159            } else {
160                None
161            },
162        }
163    }
164
165    pub(crate) fn stack(&self) -> &Stack {
166        &self.mod_local.stack
167    }
168
169    pub(crate) fn mut_stack(&mut self) -> &mut Stack {
170        &mut self.mod_local.stack
171    }
172
173    pub fn next_uuid(&mut self) -> Uuid {
174        self.mod_local.id_generator.next_uuid()
175    }
176
177    pub fn id_generator(&mut self) -> &mut IdGenerator {
178        &mut self.mod_local.id_generator
179    }
180
181    pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
182        let id = artifact.id();
183        self.global.artifacts.insert(id, artifact);
184    }
185
186    pub(super) fn next_module_id(&self) -> ModuleId {
187        ModuleId::from_usize(self.global.path_to_source_id.len())
188    }
189
190    pub(super) fn id_for_module(&self, path: &ModulePath) -> Option<ModuleId> {
191        self.global.path_to_source_id.get(path).cloned()
192    }
193
194    pub(super) fn add_path_to_source_id(&mut self, path: ModulePath, id: ModuleId) {
195        debug_assert!(!self.global.path_to_source_id.contains_key(&path));
196        self.global.path_to_source_id.insert(path.clone(), id);
197    }
198
199    pub(crate) fn add_root_module_contents(&mut self, program: &crate::Program) {
200        let root_id = ModuleId::default();
201        // Get the path for the root module.
202        let path = self
203            .global
204            .path_to_source_id
205            .iter()
206            .find(|(_, v)| **v == root_id)
207            .unwrap()
208            .0
209            .clone();
210        self.add_id_to_source(
211            root_id,
212            ModuleSource {
213                path,
214                source: program.original_file_contents.to_string(),
215            },
216        );
217    }
218
219    pub(super) fn add_id_to_source(&mut self, id: ModuleId, source: ModuleSource) {
220        self.global.id_to_source.insert(id, source.clone());
221    }
222
223    pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
224        debug_assert!(self.global.path_to_source_id.contains_key(&path));
225        let module_info = ModuleInfo { id, repr, path };
226        self.global.module_infos.insert(id, module_info);
227    }
228
229    pub fn length_unit(&self) -> UnitLen {
230        self.mod_local.settings.default_length_units
231    }
232
233    pub fn angle_unit(&self) -> UnitAngle {
234        self.mod_local.settings.default_angle_units
235    }
236
237    pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
238        KclError::ImportCycle(KclErrorDetails {
239            message: format!(
240                "circular import of modules is not allowed: {} -> {}",
241                self.global
242                    .mod_loader
243                    .import_stack
244                    .iter()
245                    .map(|p| p.as_path().to_string_lossy())
246                    .collect::<Vec<_>>()
247                    .join(" -> "),
248                path,
249            ),
250            source_ranges: vec![source_range],
251        })
252    }
253}
254
255impl GlobalState {
256    fn new(settings: &ExecutorSettings) -> Self {
257        let mut global = GlobalState {
258            path_to_source_id: Default::default(),
259            module_infos: Default::default(),
260            artifacts: Default::default(),
261            artifact_commands: Default::default(),
262            artifact_responses: Default::default(),
263            artifact_graph: Default::default(),
264            operations: Default::default(),
265            mod_loader: Default::default(),
266            errors: Default::default(),
267            id_to_source: Default::default(),
268        };
269
270        let root_id = ModuleId::default();
271        let root_path = settings.current_file.clone().unwrap_or_default();
272        global.module_infos.insert(
273            root_id,
274            ModuleInfo {
275                id: root_id,
276                path: ModulePath::Local {
277                    value: root_path.clone(),
278                },
279                repr: ModuleRepr::Root,
280            },
281        );
282        global
283            .path_to_source_id
284            .insert(ModulePath::Local { value: root_path }, root_id);
285        global
286    }
287}
288
289impl ModuleState {
290    pub(super) fn new(
291        exec_settings: &ExecutorSettings,
292        std_path: Option<String>,
293        memory: Arc<ProgramMemory>,
294        module_id: Option<ModuleId>,
295    ) -> Self {
296        ModuleState {
297            id_generator: IdGenerator::new(module_id),
298            stack: memory.new_stack(),
299            pipe_value: Default::default(),
300            module_exports: Default::default(),
301            explicit_length_units: false,
302            std_path,
303            settings: MetaSettings {
304                default_length_units: exec_settings.units.into(),
305                default_angle_units: Default::default(),
306            },
307        }
308    }
309}
310
311#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
312#[ts(export)]
313#[serde(rename_all = "camelCase")]
314pub struct MetaSettings {
315    pub default_length_units: types::UnitLen,
316    pub default_angle_units: types::UnitAngle,
317}
318
319impl MetaSettings {
320    pub(crate) fn update_from_annotation(
321        &mut self,
322        annotation: &crate::parsing::ast::types::Node<Annotation>,
323    ) -> Result<bool, KclError> {
324        let properties = annotations::expect_properties(annotations::SETTINGS, annotation)?;
325
326        let mut updated_len = false;
327        for p in properties {
328            match &*p.inner.key.name {
329                annotations::SETTINGS_UNIT_LENGTH => {
330                    let value = annotations::expect_ident(&p.inner.value)?;
331                    let value = types::UnitLen::from_str(value, annotation.as_source_range())?;
332                    self.default_length_units = value;
333                    updated_len = true;
334                }
335                annotations::SETTINGS_UNIT_ANGLE => {
336                    let value = annotations::expect_ident(&p.inner.value)?;
337                    let value = types::UnitAngle::from_str(value, annotation.as_source_range())?;
338                    self.default_angle_units = value;
339                }
340                name => {
341                    return Err(KclError::Semantic(KclErrorDetails {
342                        message: format!(
343                            "Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
344                            annotations::SETTINGS_UNIT_LENGTH,
345                            annotations::SETTINGS_UNIT_ANGLE
346                        ),
347                        source_ranges: vec![annotation.as_source_range()],
348                    }))
349                }
350            }
351        }
352
353        Ok(updated_len)
354    }
355}