1use std::{str::FromStr, sync::Arc};
2
3use anyhow::Result;
4use indexmap::IndexMap;
5use serde::{Deserialize, Serialize};
6use uuid::Uuid;
7
8#[cfg(feature = "artifact-graph")]
9use crate::execution::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId};
10use crate::{
11 CompilationError, EngineManager, ExecutorContext, KclErrorWithOutputs,
12 errors::{KclError, KclErrorDetails, Severity},
13 exec::DefaultPlanes,
14 execution::{
15 EnvironmentRef, ExecOutcome, ExecutorSettings, KclValue, UnitAngle, UnitLen, annotations,
16 cad_op::Operation,
17 id_generator::IdGenerator,
18 memory::{ProgramMemory, Stack},
19 types::{self, NumericType},
20 },
21 modules::{ModuleId, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr, ModuleSource},
22 parsing::ast::types::{Annotation, NodeRef},
23 source_range::SourceRange,
24};
25
26#[derive(Debug, Clone)]
28pub struct ExecState {
29 pub(super) global: GlobalState,
30 pub(super) mod_local: ModuleState,
31}
32
33pub type ModuleInfoMap = IndexMap<ModuleId, ModuleInfo>;
34
35#[derive(Debug, Clone)]
36pub(super) struct GlobalState {
37 pub path_to_source_id: IndexMap<ModulePath, ModuleId>,
39 pub id_to_source: IndexMap<ModuleId, ModuleSource>,
41 pub module_infos: ModuleInfoMap,
43 pub mod_loader: ModuleLoader,
45 pub errors: Vec<CompilationError>,
47 pub artifacts: ArtifactState,
49 pub root_module_artifacts: ModuleArtifactState,
51}
52
53#[cfg(feature = "artifact-graph")]
54#[derive(Debug, Clone, Default)]
55pub(super) struct ArtifactState {
56 pub artifacts: IndexMap<ArtifactId, Artifact>,
59 pub graph: ArtifactGraph,
61}
62
63#[cfg(not(feature = "artifact-graph"))]
64#[derive(Debug, Clone, Default)]
65pub(super) struct ArtifactState {}
66
67#[cfg(feature = "artifact-graph")]
69#[derive(Debug, Clone, Default, PartialEq, Serialize)]
70pub struct ModuleArtifactState {
71 pub artifacts: IndexMap<ArtifactId, Artifact>,
73 #[serde(skip)]
76 pub unprocessed_commands: Vec<ArtifactCommand>,
77 pub commands: Vec<ArtifactCommand>,
79 pub operations: Vec<Operation>,
82}
83
84#[cfg(not(feature = "artifact-graph"))]
85#[derive(Debug, Clone, Default, PartialEq, Serialize)]
86pub struct ModuleArtifactState {}
87
88#[derive(Debug, Clone)]
89pub(super) struct ModuleState {
90 pub id_generator: IdGenerator,
92 pub stack: Stack,
93 pub pipe_value: Option<KclValue>,
96 pub being_declared: Option<String>,
101 pub module_exports: Vec<String>,
103 pub settings: MetaSettings,
105 pub(super) explicit_length_units: bool,
106 pub(super) path: ModulePath,
107 pub artifacts: ModuleArtifactState,
109
110 pub(super) allowed_warnings: Vec<&'static str>,
111 pub(super) denied_warnings: Vec<&'static str>,
112}
113
114impl ExecState {
115 pub fn new(exec_context: &super::ExecutorContext) -> Self {
116 ExecState {
117 global: GlobalState::new(&exec_context.settings),
118 mod_local: ModuleState::new(ModulePath::Main, ProgramMemory::new(), Default::default()),
119 }
120 }
121
122 pub(super) fn reset(&mut self, exec_context: &super::ExecutorContext) {
123 let global = GlobalState::new(&exec_context.settings);
124
125 *self = ExecState {
126 global,
127 mod_local: ModuleState::new(self.mod_local.path.clone(), ProgramMemory::new(), Default::default()),
128 };
129 }
130
131 pub fn err(&mut self, e: CompilationError) {
133 self.global.errors.push(e);
134 }
135
136 pub fn warn(&mut self, mut e: CompilationError, name: &'static str) {
138 debug_assert!(annotations::WARN_VALUES.contains(&name));
139
140 if self.mod_local.allowed_warnings.contains(&name) {
141 return;
142 }
143
144 if self.mod_local.denied_warnings.contains(&name) {
145 e.severity = Severity::Error;
146 } else {
147 e.severity = Severity::Warning;
148 }
149
150 self.global.errors.push(e);
151 }
152
153 pub fn warn_experimental(&mut self, feature_name: &str, source_range: SourceRange) {
154 let Some(severity) = self.mod_local.settings.experimental_features.severity() else {
155 return;
156 };
157 let error = CompilationError {
158 source_range,
159 message: format!("Use of {feature_name} is experimental and may change or be removed."),
160 suggestion: None,
161 severity,
162 tag: crate::errors::Tag::None,
163 };
164
165 self.global.errors.push(error);
166 }
167
168 pub fn clear_units_warnings(&mut self, source_range: &SourceRange) {
169 self.global.errors = std::mem::take(&mut self.global.errors)
170 .into_iter()
171 .filter(|e| {
172 e.severity != Severity::Warning
173 || !source_range.contains_range(&e.source_range)
174 || e.tag != crate::errors::Tag::UnknownNumericUnits
175 })
176 .collect();
177 }
178
179 pub fn errors(&self) -> &[CompilationError] {
180 &self.global.errors
181 }
182
183 pub async fn into_exec_outcome(self, main_ref: EnvironmentRef, ctx: &ExecutorContext) -> ExecOutcome {
187 ExecOutcome {
190 variables: self.mod_local.variables(main_ref),
191 filenames: self.global.filenames(),
192 #[cfg(feature = "artifact-graph")]
193 operations: self.global.root_module_artifacts.operations,
194 #[cfg(feature = "artifact-graph")]
195 artifact_graph: self.global.artifacts.graph,
196 errors: self.global.errors,
197 default_planes: ctx.engine.get_default_planes().read().await.clone(),
198 }
199 }
200
201 pub(crate) fn stack(&self) -> &Stack {
202 &self.mod_local.stack
203 }
204
205 pub(crate) fn mut_stack(&mut self) -> &mut Stack {
206 &mut self.mod_local.stack
207 }
208
209 pub fn next_uuid(&mut self) -> Uuid {
210 self.mod_local.id_generator.next_uuid()
211 }
212
213 pub fn id_generator(&mut self) -> &mut IdGenerator {
214 &mut self.mod_local.id_generator
215 }
216
217 #[cfg(feature = "artifact-graph")]
218 pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
219 let id = artifact.id();
220 self.mod_local.artifacts.artifacts.insert(id, artifact);
221 }
222
223 pub(crate) fn push_op(&mut self, op: Operation) {
224 #[cfg(feature = "artifact-graph")]
225 self.mod_local.artifacts.operations.push(op.clone());
226 #[cfg(not(feature = "artifact-graph"))]
227 drop(op);
228 }
229
230 #[cfg(feature = "artifact-graph")]
231 pub(crate) fn push_command(&mut self, command: ArtifactCommand) {
232 self.mod_local.artifacts.unprocessed_commands.push(command);
233 #[cfg(not(feature = "artifact-graph"))]
234 drop(command);
235 }
236
237 pub(super) fn next_module_id(&self) -> ModuleId {
238 ModuleId::from_usize(self.global.path_to_source_id.len())
239 }
240
241 pub(super) fn id_for_module(&self, path: &ModulePath) -> Option<ModuleId> {
242 self.global.path_to_source_id.get(path).cloned()
243 }
244
245 pub(super) fn add_path_to_source_id(&mut self, path: ModulePath, id: ModuleId) {
246 debug_assert!(!self.global.path_to_source_id.contains_key(&path));
247 self.global.path_to_source_id.insert(path.clone(), id);
248 }
249
250 pub(crate) fn add_root_module_contents(&mut self, program: &crate::Program) {
251 let root_id = ModuleId::default();
252 let path = self
254 .global
255 .path_to_source_id
256 .iter()
257 .find(|(_, v)| **v == root_id)
258 .unwrap()
259 .0
260 .clone();
261 self.add_id_to_source(
262 root_id,
263 ModuleSource {
264 path,
265 source: program.original_file_contents.to_string(),
266 },
267 );
268 }
269
270 pub(super) fn add_id_to_source(&mut self, id: ModuleId, source: ModuleSource) {
271 self.global.id_to_source.insert(id, source.clone());
272 }
273
274 pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
275 debug_assert!(self.global.path_to_source_id.contains_key(&path));
276 let module_info = ModuleInfo { id, repr, path };
277 self.global.module_infos.insert(id, module_info);
278 }
279
280 pub fn get_module(&mut self, id: ModuleId) -> Option<&ModuleInfo> {
281 self.global.module_infos.get(&id)
282 }
283
284 #[cfg(all(test, feature = "artifact-graph"))]
285 pub(crate) fn modules(&self) -> &ModuleInfoMap {
286 &self.global.module_infos
287 }
288
289 #[cfg(all(test, feature = "artifact-graph"))]
290 pub(crate) fn root_module_artifact_state(&self) -> &ModuleArtifactState {
291 &self.global.root_module_artifacts
292 }
293
294 pub fn current_default_units(&self) -> NumericType {
295 NumericType::Default {
296 len: self.length_unit(),
297 angle: self.angle_unit(),
298 }
299 }
300
301 pub fn length_unit(&self) -> UnitLen {
302 self.mod_local.settings.default_length_units
303 }
304
305 pub fn angle_unit(&self) -> UnitAngle {
306 self.mod_local.settings.default_angle_units
307 }
308
309 pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
310 KclError::new_import_cycle(KclErrorDetails::new(
311 format!(
312 "circular import of modules is not allowed: {} -> {}",
313 self.global
314 .mod_loader
315 .import_stack
316 .iter()
317 .map(|p| p.to_string_lossy())
318 .collect::<Vec<_>>()
319 .join(" -> "),
320 path,
321 ),
322 vec![source_range],
323 ))
324 }
325
326 pub(crate) fn pipe_value(&self) -> Option<&KclValue> {
327 self.mod_local.pipe_value.as_ref()
328 }
329
330 pub(crate) fn error_with_outputs(
331 &self,
332 error: KclError,
333 main_ref: Option<EnvironmentRef>,
334 default_planes: Option<DefaultPlanes>,
335 ) -> KclErrorWithOutputs {
336 let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = self
337 .global
338 .path_to_source_id
339 .iter()
340 .map(|(k, v)| ((*v), k.clone()))
341 .collect();
342
343 KclErrorWithOutputs::new(
344 error,
345 self.errors().to_vec(),
346 main_ref
347 .map(|main_ref| self.mod_local.variables(main_ref))
348 .unwrap_or_default(),
349 #[cfg(feature = "artifact-graph")]
350 self.global.root_module_artifacts.operations.clone(),
351 #[cfg(feature = "artifact-graph")]
352 Default::default(),
353 #[cfg(feature = "artifact-graph")]
354 self.global.artifacts.graph.clone(),
355 module_id_to_module_path,
356 self.global.id_to_source.clone(),
357 default_planes,
358 )
359 }
360
361 #[cfg(feature = "artifact-graph")]
362 pub(crate) async fn build_artifact_graph(
363 &mut self,
364 engine: &Arc<Box<dyn EngineManager>>,
365 program: NodeRef<'_, crate::parsing::ast::types::Program>,
366 ) -> Result<(), KclError> {
367 let mut new_commands = Vec::new();
368 let mut new_exec_artifacts = IndexMap::new();
369 for module in self.global.module_infos.values_mut() {
370 match &mut module.repr {
371 ModuleRepr::Kcl(_, Some((_, _, _, module_artifacts)))
372 | ModuleRepr::Foreign(_, Some((_, module_artifacts))) => {
373 new_commands.extend(module_artifacts.process_commands());
374 new_exec_artifacts.extend(module_artifacts.artifacts.clone());
375 }
376 ModuleRepr::Root | ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
377 }
378 }
379 new_commands.extend(self.global.root_module_artifacts.process_commands());
382 new_exec_artifacts.extend(self.global.root_module_artifacts.artifacts.clone());
385 let new_responses = engine.take_responses().await;
386
387 self.global.artifacts.artifacts.extend(new_exec_artifacts);
390
391 let initial_graph = self.global.artifacts.graph.clone();
392
393 let graph_result = crate::execution::artifact::build_artifact_graph(
395 &new_commands,
396 &new_responses,
397 program,
398 &mut self.global.artifacts.artifacts,
399 initial_graph,
400 );
401
402 let artifact_graph = graph_result?;
403 self.global.artifacts.graph = artifact_graph;
404
405 Ok(())
406 }
407
408 #[cfg(not(feature = "artifact-graph"))]
409 pub(crate) async fn build_artifact_graph(
410 &mut self,
411 _engine: &Arc<Box<dyn EngineManager>>,
412 _program: NodeRef<'_, crate::parsing::ast::types::Program>,
413 ) -> Result<(), KclError> {
414 Ok(())
415 }
416}
417
418impl GlobalState {
419 fn new(settings: &ExecutorSettings) -> Self {
420 let mut global = GlobalState {
421 path_to_source_id: Default::default(),
422 module_infos: Default::default(),
423 artifacts: Default::default(),
424 root_module_artifacts: Default::default(),
425 mod_loader: Default::default(),
426 errors: Default::default(),
427 id_to_source: Default::default(),
428 };
429
430 let root_id = ModuleId::default();
431 let root_path = settings.current_file.clone().unwrap_or_default();
432 global.module_infos.insert(
433 root_id,
434 ModuleInfo {
435 id: root_id,
436 path: ModulePath::Local {
437 value: root_path.clone(),
438 },
439 repr: ModuleRepr::Root,
440 },
441 );
442 global
443 .path_to_source_id
444 .insert(ModulePath::Local { value: root_path }, root_id);
445 global
446 }
447
448 pub(super) fn filenames(&self) -> IndexMap<ModuleId, ModulePath> {
449 self.path_to_source_id.iter().map(|(k, v)| ((*v), k.clone())).collect()
450 }
451
452 pub(super) fn get_source(&self, id: ModuleId) -> Option<&ModuleSource> {
453 self.id_to_source.get(&id)
454 }
455}
456
457impl ArtifactState {
458 #[cfg(feature = "artifact-graph")]
459 pub fn cached_body_items(&self) -> usize {
460 self.graph.item_count
461 }
462
463 pub(crate) fn clear(&mut self) {
464 #[cfg(feature = "artifact-graph")]
465 {
466 self.artifacts.clear();
467 self.graph.clear();
468 }
469 }
470}
471
472impl ModuleArtifactState {
473 pub(crate) fn clear(&mut self) {
474 #[cfg(feature = "artifact-graph")]
475 {
476 self.artifacts.clear();
477 self.unprocessed_commands.clear();
478 self.commands.clear();
479 self.operations.clear();
480 }
481 }
482
483 #[cfg(not(feature = "artifact-graph"))]
484 pub(crate) fn extend(&mut self, _other: ModuleArtifactState) {}
485
486 #[cfg(feature = "artifact-graph")]
488 pub(crate) fn extend(&mut self, other: ModuleArtifactState) {
489 self.artifacts.extend(other.artifacts);
490 self.unprocessed_commands.extend(other.unprocessed_commands);
491 self.commands.extend(other.commands);
492 self.operations.extend(other.operations);
493 }
494
495 #[cfg(feature = "artifact-graph")]
499 pub(crate) fn process_commands(&mut self) -> Vec<ArtifactCommand> {
500 let unprocessed = std::mem::take(&mut self.unprocessed_commands);
501 let new_module_commands = unprocessed.clone();
502 self.commands.extend(unprocessed);
503 new_module_commands
504 }
505}
506
507impl ModuleState {
508 pub(super) fn new(path: ModulePath, memory: Arc<ProgramMemory>, module_id: Option<ModuleId>) -> Self {
509 ModuleState {
510 id_generator: IdGenerator::new(module_id),
511 stack: memory.new_stack(),
512 pipe_value: Default::default(),
513 being_declared: Default::default(),
514 module_exports: Default::default(),
515 explicit_length_units: false,
516 path,
517 settings: Default::default(),
518 artifacts: Default::default(),
519 allowed_warnings: Vec::new(),
520 denied_warnings: Vec::new(),
521 }
522 }
523
524 pub(super) fn variables(&self, main_ref: EnvironmentRef) -> IndexMap<String, KclValue> {
525 self.stack
526 .find_all_in_env(main_ref)
527 .map(|(k, v)| (k.clone(), v.clone()))
528 .collect()
529 }
530}
531
532#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
533#[ts(export)]
534#[serde(rename_all = "camelCase")]
535pub struct MetaSettings {
536 pub default_length_units: types::UnitLen,
537 pub default_angle_units: types::UnitAngle,
538 pub experimental_features: annotations::WarningLevel,
539 pub kcl_version: String,
540}
541
542impl Default for MetaSettings {
543 fn default() -> Self {
544 MetaSettings {
545 default_length_units: Default::default(),
546 default_angle_units: Default::default(),
547 experimental_features: annotations::WarningLevel::Deny,
548 kcl_version: "1.0".to_owned(),
549 }
550 }
551}
552
553impl MetaSettings {
554 pub(crate) fn update_from_annotation(
555 &mut self,
556 annotation: &crate::parsing::ast::types::Node<Annotation>,
557 ) -> Result<bool, KclError> {
558 let properties = annotations::expect_properties(annotations::SETTINGS, annotation)?;
559
560 let mut updated_len = false;
561 for p in properties {
562 match &*p.inner.key.name {
563 annotations::SETTINGS_UNIT_LENGTH => {
564 let value = annotations::expect_ident(&p.inner.value)?;
565 let value = types::UnitLen::from_str(value, annotation.as_source_range())?;
566 self.default_length_units = value;
567 updated_len = true;
568 }
569 annotations::SETTINGS_UNIT_ANGLE => {
570 let value = annotations::expect_ident(&p.inner.value)?;
571 let value = types::UnitAngle::from_str(value, annotation.as_source_range())?;
572 self.default_angle_units = value;
573 }
574 annotations::SETTINGS_VERSION => {
575 let value = annotations::expect_number(&p.inner.value)?;
576 self.kcl_version = value;
577 }
578 annotations::SETTINGS_EXPERIMENTAL_FEATURES => {
579 let value = annotations::expect_ident(&p.inner.value)?;
580 let value = annotations::WarningLevel::from_str(value).map_err(|_| {
581 KclError::new_semantic(KclErrorDetails::new(
582 format!(
583 "Invalid value for {} settings property, expected one of: {}",
584 annotations::SETTINGS_EXPERIMENTAL_FEATURES,
585 annotations::WARN_LEVELS.join(", ")
586 ),
587 annotation.as_source_ranges(),
588 ))
589 })?;
590 self.experimental_features = value;
591 }
592 name => {
593 return Err(KclError::new_semantic(KclErrorDetails::new(
594 format!(
595 "Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
596 annotations::SETTINGS_UNIT_LENGTH,
597 annotations::SETTINGS_UNIT_ANGLE
598 ),
599 vec![annotation.as_source_range()],
600 )));
601 }
602 }
603 }
604
605 Ok(updated_len)
606 }
607}