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, kcl_value,
14        memory::{ProgramMemory, Stack},
15        Artifact, ArtifactCommand, ArtifactGraph, ArtifactId, EnvironmentRef, ExecOutcome, ExecutorSettings, KclValue,
16        Operation, UnitAngle, UnitLen,
17    },
18    modules::{ModuleId, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr, ModuleSource},
19    parsing::ast::types::Annotation,
20    source_range::SourceRange,
21    CompilationError,
22};
23
24/// State for executing a program.
25#[derive(Debug, Clone)]
26pub struct ExecState {
27    pub(super) global: GlobalState,
28    pub(super) mod_local: ModuleState,
29}
30
31#[derive(Debug, Clone)]
32pub(super) struct GlobalState {
33    /// The stable artifact ID generator.
34    pub id_generator: IdGenerator,
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    pub stack: Stack,
66    /// The current value of the pipe operator returned from the previous
67    /// expression.  If we're not currently in a pipeline, this will be None.
68    pub pipe_value: Option<KclValue>,
69    /// Identifiers that have been exported from the current module.
70    pub module_exports: Vec<String>,
71    /// Settings specified from annotations.
72    pub settings: MetaSettings,
73}
74
75impl ExecState {
76    pub fn new(exec_settings: &ExecutorSettings) -> Self {
77        ExecState {
78            global: GlobalState::new(exec_settings),
79            mod_local: ModuleState::new(exec_settings, None, ProgramMemory::new()),
80        }
81    }
82
83    pub(super) fn reset(&mut self, exec_settings: &ExecutorSettings) {
84        let mut id_generator = self.global.id_generator.clone();
85        // We do not pop the ids, since we want to keep the same id generator.
86        // This is for the front end to keep track of the ids.
87        id_generator.next_id = 0;
88
89        let mut global = GlobalState::new(exec_settings);
90        global.id_generator = id_generator;
91
92        *self = ExecState {
93            global,
94            mod_local: ModuleState::new(exec_settings, None, ProgramMemory::new()),
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 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, |_| true)
123                .map(|(k, v)| (k.clone(), v.clone()))
124                .collect(),
125            operations: self.global.operations,
126            artifacts: self.global.artifacts,
127            artifact_commands: self.global.artifact_commands,
128            artifact_graph: self.global.artifact_graph,
129            errors: self.global.errors,
130            filenames: self
131                .global
132                .path_to_source_id
133                .iter()
134                .map(|(k, v)| ((*v), k.clone()))
135                .collect(),
136        }
137    }
138
139    pub fn to_mock_wasm_outcome(self, main_ref: EnvironmentRef) -> ExecOutcome {
140        // Fields are opt-in so that we don't accidentally leak private internal
141        // state when we add more to ExecState.
142        ExecOutcome {
143            variables: self
144                .stack()
145                .find_all_in_env(main_ref, |_| true)
146                .map(|(k, v)| (k.clone(), v.clone()))
147                .collect(),
148            operations: Default::default(),
149            artifacts: Default::default(),
150            artifact_commands: Default::default(),
151            artifact_graph: Default::default(),
152            errors: self.global.errors,
153            filenames: Default::default(),
154        }
155    }
156
157    pub(crate) fn stack(&self) -> &Stack {
158        &self.mod_local.stack
159    }
160
161    pub(crate) fn mut_stack(&mut self) -> &mut Stack {
162        &mut self.mod_local.stack
163    }
164
165    pub(crate) fn next_uuid(&mut self) -> Uuid {
166        self.global.id_generator.next_uuid()
167    }
168
169    pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
170        let id = artifact.id();
171        self.global.artifacts.insert(id, artifact);
172    }
173
174    pub(super) fn next_module_id(&self) -> ModuleId {
175        ModuleId::from_usize(self.global.path_to_source_id.len())
176    }
177
178    pub(super) fn id_for_module(&self, path: &ModulePath) -> Option<ModuleId> {
179        self.global.path_to_source_id.get(path).cloned()
180    }
181
182    pub(super) fn add_path_to_source_id(&mut self, path: ModulePath, id: ModuleId) {
183        debug_assert!(!self.global.path_to_source_id.contains_key(&path));
184        self.global.path_to_source_id.insert(path.clone(), id);
185    }
186
187    pub(crate) fn add_root_module_contents(&mut self, program: &crate::Program) {
188        let root_id = ModuleId::default();
189        // Get the path for the root module.
190        let path = self
191            .global
192            .path_to_source_id
193            .iter()
194            .find(|(_, v)| **v == root_id)
195            .unwrap()
196            .0
197            .clone();
198        self.add_id_to_source(
199            root_id,
200            ModuleSource {
201                path,
202                source: program.original_file_contents.to_string(),
203            },
204        );
205    }
206
207    pub(super) fn add_id_to_source(&mut self, id: ModuleId, source: ModuleSource) {
208        self.global.id_to_source.insert(id, source.clone());
209    }
210
211    pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
212        debug_assert!(self.global.path_to_source_id.contains_key(&path));
213        let module_info = ModuleInfo { id, repr, path };
214        self.global.module_infos.insert(id, module_info);
215    }
216
217    pub fn length_unit(&self) -> UnitLen {
218        self.mod_local.settings.default_length_units
219    }
220
221    pub fn angle_unit(&self) -> UnitAngle {
222        self.mod_local.settings.default_angle_units
223    }
224
225    pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
226        KclError::ImportCycle(KclErrorDetails {
227            message: format!(
228                "circular import of modules is not allowed: {} -> {}",
229                self.global
230                    .mod_loader
231                    .import_stack
232                    .iter()
233                    .map(|p| p.as_path().to_string_lossy())
234                    .collect::<Vec<_>>()
235                    .join(" -> "),
236                path,
237            ),
238            source_ranges: vec![source_range],
239        })
240    }
241}
242
243impl GlobalState {
244    fn new(settings: &ExecutorSettings) -> Self {
245        let mut global = GlobalState {
246            id_generator: Default::default(),
247            path_to_source_id: Default::default(),
248            module_infos: Default::default(),
249            artifacts: Default::default(),
250            artifact_commands: Default::default(),
251            artifact_responses: Default::default(),
252            artifact_graph: Default::default(),
253            operations: Default::default(),
254            mod_loader: Default::default(),
255            errors: Default::default(),
256            id_to_source: Default::default(),
257        };
258
259        let root_id = ModuleId::default();
260        let root_path = settings.current_file.clone().unwrap_or_default();
261        global.module_infos.insert(
262            root_id,
263            ModuleInfo {
264                id: root_id,
265                path: ModulePath::Local {
266                    value: root_path.clone(),
267                },
268                repr: ModuleRepr::Root,
269            },
270        );
271        global
272            .path_to_source_id
273            .insert(ModulePath::Local { value: root_path }, root_id);
274        global
275    }
276}
277
278impl ModuleState {
279    pub(super) fn new(exec_settings: &ExecutorSettings, std_path: Option<String>, memory: Arc<ProgramMemory>) -> Self {
280        ModuleState {
281            stack: memory.new_stack(),
282            pipe_value: Default::default(),
283            module_exports: Default::default(),
284            settings: MetaSettings {
285                default_length_units: exec_settings.units.into(),
286                default_angle_units: Default::default(),
287                std_path,
288            },
289        }
290    }
291}
292
293#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
294#[ts(export)]
295#[serde(rename_all = "camelCase")]
296pub struct MetaSettings {
297    pub default_length_units: kcl_value::UnitLen,
298    pub default_angle_units: kcl_value::UnitAngle,
299    pub std_path: Option<String>,
300}
301
302impl MetaSettings {
303    pub(crate) fn update_from_annotation(
304        &mut self,
305        annotation: &crate::parsing::ast::types::Node<Annotation>,
306    ) -> Result<(), KclError> {
307        let properties = annotations::expect_properties(annotations::SETTINGS, annotation)?;
308
309        for p in properties {
310            match &*p.inner.key.name {
311                annotations::SETTINGS_UNIT_LENGTH => {
312                    let value = annotations::expect_ident(&p.inner.value)?;
313                    let value = kcl_value::UnitLen::from_str(value, annotation.as_source_range())?;
314                    self.default_length_units = value;
315                }
316                annotations::SETTINGS_UNIT_ANGLE => {
317                    let value = annotations::expect_ident(&p.inner.value)?;
318                    let value = kcl_value::UnitAngle::from_str(value, annotation.as_source_range())?;
319                    self.default_angle_units = value;
320                }
321                name => {
322                    return Err(KclError::Semantic(KclErrorDetails {
323                        message: format!(
324                            "Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
325                            annotations::SETTINGS_UNIT_LENGTH,
326                            annotations::SETTINGS_UNIT_ANGLE
327                        ),
328                        source_ranges: vec![annotation.as_source_range()],
329                    }))
330                }
331            }
332        }
333
334        Ok(())
335    }
336}
337
338/// A generator for ArtifactIds that can be stable across executions.
339#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
340#[serde(rename_all = "camelCase")]
341pub struct IdGenerator {
342    pub(super) next_id: usize,
343    ids: Vec<uuid::Uuid>,
344}
345
346impl IdGenerator {
347    pub fn new() -> Self {
348        Self::default()
349    }
350
351    pub fn next_uuid(&mut self) -> uuid::Uuid {
352        if let Some(id) = self.ids.get(self.next_id) {
353            self.next_id += 1;
354            *id
355        } else {
356            let id = uuid::Uuid::new_v4();
357            self.ids.push(id);
358            self.next_id += 1;
359            id
360        }
361    }
362}