kcl_lib/execution/
state.rs

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