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