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