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