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 exec::DefaultPlanes,
16 execution::{
17 annotations,
18 cad_op::Operation,
19 id_generator::IdGenerator,
20 memory::{ProgramMemory, Stack},
21 types::{self, NumericType},
22 EnvironmentRef, ExecOutcome, ExecutorSettings, KclValue, UnitAngle, UnitLen,
23 },
24 modules::{ModuleId, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr, ModuleSource},
25 parsing::ast::types::{Annotation, NodeRef},
26 source_range::SourceRange,
27 CompilationError, EngineManager, ExecutorContext, KclErrorWithOutputs,
28};
29
30#[derive(Debug, Clone)]
32pub struct ExecState {
33 pub(super) global: GlobalState,
34 pub(super) mod_local: ModuleState,
35}
36
37pub type ModuleInfoMap = IndexMap<ModuleId, ModuleInfo>;
38
39#[derive(Debug, Clone)]
40pub(super) struct GlobalState {
41 pub path_to_source_id: IndexMap<ModulePath, ModuleId>,
43 pub id_to_source: IndexMap<ModuleId, ModuleSource>,
45 pub module_infos: ModuleInfoMap,
47 pub mod_loader: ModuleLoader,
49 pub errors: Vec<CompilationError>,
51 #[allow(dead_code)]
52 pub artifacts: ArtifactState,
53}
54
55#[cfg(feature = "artifact-graph")]
56#[derive(Debug, Clone, Default)]
57pub(super) struct ArtifactState {
58 pub artifacts: IndexMap<ArtifactId, Artifact>,
60 pub commands: Vec<ArtifactCommand>,
64 pub responses: IndexMap<Uuid, WebSocketResponse>,
69 pub graph: ArtifactGraph,
71 pub operations: Vec<Operation>,
74}
75
76#[cfg(not(feature = "artifact-graph"))]
77#[derive(Debug, Clone, Default)]
78pub(super) struct ArtifactState {}
79
80#[derive(Debug, Clone)]
81pub(super) struct ModuleState {
82 pub id_generator: IdGenerator,
84 pub stack: Stack,
85 pub pipe_value: Option<KclValue>,
88 pub being_declared: Option<String>,
93 pub module_exports: Vec<String>,
95 pub settings: MetaSettings,
97 pub(super) explicit_length_units: bool,
98 pub(super) path: ModulePath,
99}
100
101impl ExecState {
102 pub fn new(exec_context: &super::ExecutorContext) -> Self {
103 ExecState {
104 global: GlobalState::new(&exec_context.settings),
105 mod_local: ModuleState::new(ModulePath::Main, ProgramMemory::new(), Default::default()),
106 }
107 }
108
109 pub(super) fn reset(&mut self, exec_context: &super::ExecutorContext) {
110 let global = GlobalState::new(&exec_context.settings);
111
112 *self = ExecState {
113 global,
114 mod_local: ModuleState::new(self.mod_local.path.clone(), ProgramMemory::new(), Default::default()),
115 };
116 }
117
118 pub fn err(&mut self, e: CompilationError) {
120 self.global.errors.push(e);
121 }
122
123 pub fn warn(&mut self, mut e: CompilationError) {
125 e.severity = Severity::Warning;
126 self.global.errors.push(e);
127 }
128
129 pub fn errors(&self) -> &[CompilationError] {
130 &self.global.errors
131 }
132
133 pub async fn to_exec_outcome(self, main_ref: EnvironmentRef, ctx: &ExecutorContext) -> ExecOutcome {
137 ExecOutcome {
140 variables: self.mod_local.variables(main_ref),
141 filenames: self.global.filenames(),
142 #[cfg(feature = "artifact-graph")]
143 operations: self.global.artifacts.operations,
144 #[cfg(feature = "artifact-graph")]
145 artifact_commands: self.global.artifacts.commands,
146 #[cfg(feature = "artifact-graph")]
147 artifact_graph: self.global.artifacts.graph,
148 errors: self.global.errors,
149 default_planes: ctx.engine.get_default_planes().read().await.clone(),
150 }
151 }
152
153 pub async fn to_mock_exec_outcome(self, main_ref: EnvironmentRef, ctx: &ExecutorContext) -> ExecOutcome {
154 ExecOutcome {
155 variables: self.mod_local.variables(main_ref),
156 #[cfg(feature = "artifact-graph")]
157 operations: Default::default(),
158 #[cfg(feature = "artifact-graph")]
159 artifact_commands: Default::default(),
160 #[cfg(feature = "artifact-graph")]
161 artifact_graph: Default::default(),
162 errors: self.global.errors,
163 filenames: Default::default(),
164 default_planes: ctx.engine.get_default_planes().read().await.clone(),
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 #[cfg(feature = "artifact-graph")]
185 pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
186 let id = artifact.id();
187 self.global.artifacts.artifacts.insert(id, artifact);
188 }
189
190 pub(crate) fn push_op(&mut self, op: Operation) {
191 #[cfg(feature = "artifact-graph")]
192 self.global.artifacts.operations.push(op);
193 #[cfg(not(feature = "artifact-graph"))]
194 drop(op);
195 }
196
197 pub(super) fn next_module_id(&self) -> ModuleId {
198 ModuleId::from_usize(self.global.path_to_source_id.len())
199 }
200
201 pub(super) fn id_for_module(&self, path: &ModulePath) -> Option<ModuleId> {
202 self.global.path_to_source_id.get(path).cloned()
203 }
204
205 pub(super) fn add_path_to_source_id(&mut self, path: ModulePath, id: ModuleId) {
206 debug_assert!(!self.global.path_to_source_id.contains_key(&path));
207 self.global.path_to_source_id.insert(path.clone(), id);
208 }
209
210 pub(crate) fn add_root_module_contents(&mut self, program: &crate::Program) {
211 let root_id = ModuleId::default();
212 let path = self
214 .global
215 .path_to_source_id
216 .iter()
217 .find(|(_, v)| **v == root_id)
218 .unwrap()
219 .0
220 .clone();
221 self.add_id_to_source(
222 root_id,
223 ModuleSource {
224 path,
225 source: program.original_file_contents.to_string(),
226 },
227 );
228 }
229
230 pub(super) fn add_id_to_source(&mut self, id: ModuleId, source: ModuleSource) {
231 self.global.id_to_source.insert(id, source.clone());
232 }
233
234 pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
235 debug_assert!(self.global.path_to_source_id.contains_key(&path));
236 let module_info = ModuleInfo { id, repr, path };
237 self.global.module_infos.insert(id, module_info);
238 }
239
240 pub fn get_module(&mut self, id: ModuleId) -> Option<&ModuleInfo> {
241 self.global.module_infos.get(&id)
242 }
243
244 pub fn current_default_units(&self) -> NumericType {
245 NumericType::Default {
246 len: self.length_unit(),
247 angle: self.angle_unit(),
248 }
249 }
250
251 pub fn length_unit(&self) -> UnitLen {
252 self.mod_local.settings.default_length_units
253 }
254
255 pub fn angle_unit(&self) -> UnitAngle {
256 self.mod_local.settings.default_angle_units
257 }
258
259 pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
260 KclError::new_import_cycle(KclErrorDetails::new(
261 format!(
262 "circular import of modules is not allowed: {} -> {}",
263 self.global
264 .mod_loader
265 .import_stack
266 .iter()
267 .map(|p| p.to_string_lossy())
268 .collect::<Vec<_>>()
269 .join(" -> "),
270 path,
271 ),
272 vec![source_range],
273 ))
274 }
275
276 pub(crate) fn pipe_value(&self) -> Option<&KclValue> {
277 self.mod_local.pipe_value.as_ref()
278 }
279
280 pub(crate) fn error_with_outputs(
281 &self,
282 error: KclError,
283 default_planes: Option<DefaultPlanes>,
284 ) -> KclErrorWithOutputs {
285 let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = self
286 .global
287 .path_to_source_id
288 .iter()
289 .map(|(k, v)| ((*v), k.clone()))
290 .collect();
291
292 KclErrorWithOutputs::new(
293 error,
294 self.errors().to_vec(),
295 #[cfg(feature = "artifact-graph")]
296 self.global.artifacts.operations.clone(),
297 #[cfg(feature = "artifact-graph")]
298 self.global.artifacts.commands.clone(),
299 #[cfg(feature = "artifact-graph")]
300 self.global.artifacts.graph.clone(),
301 module_id_to_module_path,
302 self.global.id_to_source.clone(),
303 default_planes,
304 )
305 }
306
307 #[cfg(feature = "artifact-graph")]
308 pub(crate) async fn build_artifact_graph(
309 &mut self,
310 engine: &Arc<Box<dyn EngineManager>>,
311 program: NodeRef<'_, crate::parsing::ast::types::Program>,
312 ) -> Result<(), KclError> {
313 let new_commands = engine.take_artifact_commands().await;
314 let new_responses = engine.take_responses().await;
315 let initial_graph = self.global.artifacts.graph.clone();
316
317 let graph_result = crate::execution::artifact::build_artifact_graph(
319 &new_commands,
320 &new_responses,
321 program,
322 &mut self.global.artifacts.artifacts,
323 initial_graph,
324 );
325 self.global.artifacts.commands.extend(new_commands);
328 self.global.artifacts.responses.extend(new_responses);
329
330 let artifact_graph = graph_result?;
331 self.global.artifacts.graph = artifact_graph;
332
333 Ok(())
334 }
335
336 #[cfg(not(feature = "artifact-graph"))]
337 pub(crate) async fn build_artifact_graph(
338 &mut self,
339 _engine: &Arc<Box<dyn EngineManager>>,
340 _program: NodeRef<'_, crate::parsing::ast::types::Program>,
341 ) -> Result<(), KclError> {
342 Ok(())
343 }
344}
345
346impl GlobalState {
347 fn new(settings: &ExecutorSettings) -> Self {
348 let mut global = GlobalState {
349 path_to_source_id: Default::default(),
350 module_infos: Default::default(),
351 artifacts: Default::default(),
352 mod_loader: Default::default(),
353 errors: Default::default(),
354 id_to_source: Default::default(),
355 };
356
357 let root_id = ModuleId::default();
358 let root_path = settings.current_file.clone().unwrap_or_default();
359 global.module_infos.insert(
360 root_id,
361 ModuleInfo {
362 id: root_id,
363 path: ModulePath::Local {
364 value: root_path.clone(),
365 },
366 repr: ModuleRepr::Root,
367 },
368 );
369 global
370 .path_to_source_id
371 .insert(ModulePath::Local { value: root_path }, root_id);
372 global
373 }
374
375 pub(super) fn filenames(&self) -> IndexMap<ModuleId, ModulePath> {
376 self.path_to_source_id.iter().map(|(k, v)| ((*v), k.clone())).collect()
377 }
378
379 pub(super) fn get_source(&self, id: ModuleId) -> Option<&ModuleSource> {
380 self.id_to_source.get(&id)
381 }
382}
383
384#[cfg(feature = "artifact-graph")]
385impl ArtifactState {
386 pub fn cached_body_items(&self) -> usize {
387 self.graph.item_count
388 }
389}
390
391impl ModuleState {
392 pub(super) fn new(path: ModulePath, memory: Arc<ProgramMemory>, module_id: Option<ModuleId>) -> Self {
393 ModuleState {
394 id_generator: IdGenerator::new(module_id),
395 stack: memory.new_stack(),
396 pipe_value: Default::default(),
397 being_declared: Default::default(),
398 module_exports: Default::default(),
399 explicit_length_units: false,
400 path,
401 settings: MetaSettings {
402 default_length_units: Default::default(),
403 default_angle_units: Default::default(),
404 kcl_version: "0.1".to_owned(),
405 },
406 }
407 }
408
409 pub(super) fn variables(&self, main_ref: EnvironmentRef) -> IndexMap<String, KclValue> {
410 self.stack
411 .find_all_in_env(main_ref)
412 .map(|(k, v)| (k.clone(), v.clone()))
413 .collect()
414 }
415}
416
417#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, JsonSchema)]
418#[ts(export)]
419#[serde(rename_all = "camelCase")]
420pub struct MetaSettings {
421 pub default_length_units: types::UnitLen,
422 pub default_angle_units: types::UnitAngle,
423 pub kcl_version: String,
424}
425
426impl MetaSettings {
427 pub(crate) fn update_from_annotation(
428 &mut self,
429 annotation: &crate::parsing::ast::types::Node<Annotation>,
430 ) -> Result<bool, KclError> {
431 let properties = annotations::expect_properties(annotations::SETTINGS, annotation)?;
432
433 let mut updated_len = false;
434 for p in properties {
435 match &*p.inner.key.name {
436 annotations::SETTINGS_UNIT_LENGTH => {
437 let value = annotations::expect_ident(&p.inner.value)?;
438 let value = types::UnitLen::from_str(value, annotation.as_source_range())?;
439 self.default_length_units = value;
440 updated_len = true;
441 }
442 annotations::SETTINGS_UNIT_ANGLE => {
443 let value = annotations::expect_ident(&p.inner.value)?;
444 let value = types::UnitAngle::from_str(value, annotation.as_source_range())?;
445 self.default_angle_units = value;
446 }
447 annotations::SETTINGS_VERSION => {
448 let value = annotations::expect_number(&p.inner.value)?;
449 self.kcl_version = value;
450 }
451 name => {
452 return Err(KclError::new_semantic(KclErrorDetails::new(
453 format!(
454 "Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
455 annotations::SETTINGS_UNIT_LENGTH,
456 annotations::SETTINGS_UNIT_ANGLE
457 ),
458 vec![annotation.as_source_range()],
459 )))
460 }
461 }
462 }
463
464 Ok(updated_len)
465 }
466}