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(super) fn add_id_to_source(&mut self, id: ModuleId, source: ModuleSource) {
186        debug_assert!(!self.global.id_to_source.contains_key(&id));
187        self.global.id_to_source.insert(id, source.clone());
188    }
189
190    pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
191        debug_assert!(self.global.path_to_source_id.contains_key(&path));
192        let module_info = ModuleInfo { id, repr, path };
193        self.global.module_infos.insert(id, module_info);
194    }
195
196    pub fn length_unit(&self) -> UnitLen {
197        self.mod_local.settings.default_length_units
198    }
199
200    pub fn angle_unit(&self) -> UnitAngle {
201        self.mod_local.settings.default_angle_units
202    }
203
204    pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
205        KclError::ImportCycle(KclErrorDetails {
206            message: format!(
207                "circular import of modules is not allowed: {} -> {}",
208                self.global
209                    .mod_loader
210                    .import_stack
211                    .iter()
212                    .map(|p| p.as_path().to_string_lossy())
213                    .collect::<Vec<_>>()
214                    .join(" -> "),
215                path,
216            ),
217            source_ranges: vec![source_range],
218        })
219    }
220}
221
222impl GlobalState {
223    fn new(settings: &ExecutorSettings) -> Self {
224        let mut global = GlobalState {
225            memory: ProgramMemory::new(),
226            id_generator: Default::default(),
227            path_to_source_id: Default::default(),
228            module_infos: Default::default(),
229            artifacts: Default::default(),
230            artifact_commands: Default::default(),
231            artifact_responses: Default::default(),
232            artifact_graph: Default::default(),
233            operations: Default::default(),
234            mod_loader: Default::default(),
235            errors: Default::default(),
236            id_to_source: Default::default(),
237        };
238
239        let root_id = ModuleId::default();
240        let root_path = settings.current_file.clone().unwrap_or_default();
241        global.module_infos.insert(
242            root_id,
243            ModuleInfo {
244                id: root_id,
245                path: ModulePath::Local {
246                    value: root_path.clone(),
247                },
248                repr: ModuleRepr::Root,
249            },
250        );
251        global
252            .path_to_source_id
253            .insert(ModulePath::Local { value: root_path }, root_id);
254        // Ideally we'd have a way to set the root module's source here, but
255        // we don't have a way to get the source from the executor settings.
256        global
257    }
258}
259
260impl ModuleState {
261    pub(super) fn new(exec_settings: &ExecutorSettings, std_path: Option<String>) -> Self {
262        ModuleState {
263            pipe_value: Default::default(),
264            module_exports: Default::default(),
265            settings: MetaSettings {
266                default_length_units: exec_settings.units.into(),
267                default_angle_units: Default::default(),
268                std_path,
269            },
270        }
271    }
272}
273
274#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
275#[ts(export)]
276#[serde(rename_all = "camelCase")]
277pub struct MetaSettings {
278    pub default_length_units: kcl_value::UnitLen,
279    pub default_angle_units: kcl_value::UnitAngle,
280    pub std_path: Option<String>,
281}
282
283impl MetaSettings {
284    pub(crate) fn update_from_annotation(
285        &mut self,
286        annotation: &crate::parsing::ast::types::Node<Annotation>,
287    ) -> Result<(), KclError> {
288        let properties = annotations::expect_properties(annotations::SETTINGS, annotation)?;
289
290        for p in properties {
291            match &*p.inner.key.name {
292                annotations::SETTINGS_UNIT_LENGTH => {
293                    let value = annotations::expect_ident(&p.inner.value)?;
294                    let value = kcl_value::UnitLen::from_str(value, annotation.as_source_range())?;
295                    self.default_length_units = value;
296                }
297                annotations::SETTINGS_UNIT_ANGLE => {
298                    let value = annotations::expect_ident(&p.inner.value)?;
299                    let value = kcl_value::UnitAngle::from_str(value, annotation.as_source_range())?;
300                    self.default_angle_units = value;
301                }
302                name => {
303                    return Err(KclError::Semantic(KclErrorDetails {
304                        message: format!(
305                            "Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
306                            annotations::SETTINGS_UNIT_LENGTH,
307                            annotations::SETTINGS_UNIT_ANGLE
308                        ),
309                        source_ranges: vec![annotation.as_source_range()],
310                    }))
311                }
312            }
313        }
314
315        Ok(())
316    }
317}
318
319/// A generator for ArtifactIds that can be stable across executions.
320#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
321#[serde(rename_all = "camelCase")]
322pub struct IdGenerator {
323    pub(super) next_id: usize,
324    ids: Vec<uuid::Uuid>,
325}
326
327impl IdGenerator {
328    pub fn new() -> Self {
329        Self::default()
330    }
331
332    pub fn next_uuid(&mut self) -> uuid::Uuid {
333        if let Some(id) = self.ids.get(self.next_id) {
334            self.next_id += 1;
335            *id
336        } else {
337            let id = uuid::Uuid::new_v4();
338            self.ids.push(id);
339            self.next_id += 1;
340            id
341        }
342    }
343}