kcl_lib/execution/
state.rs

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