1use std::fmt;
2
3use anyhow::Result;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7use crate::{
8 errors::{KclError, KclErrorDetails},
9 exec::KclValue,
10 execution::{EnvironmentRef, ModuleArtifactState, PreImportedGeometry, typed_path::TypedPath},
11 fs::{FileManager, FileSystem},
12 parsing::ast::types::{ImportPath, Node, Program},
13 source_range::SourceRange,
14};
15
16#[derive(
18 Debug, Default, Ord, PartialOrd, Eq, PartialEq, Clone, Copy, Hash, Deserialize, Serialize, ts_rs::TS, JsonSchema,
19)]
20#[ts(export)]
21pub struct ModuleId(u32);
22
23impl ModuleId {
24 pub fn from_usize(id: usize) -> Self {
25 Self(u32::try_from(id).expect("module ID should fit in a u32"))
26 }
27
28 pub fn as_usize(&self) -> usize {
29 usize::try_from(self.0).expect("module ID should fit in a usize")
30 }
31
32 pub fn is_top_level(&self) -> bool {
35 *self == Self::default()
36 }
37}
38
39impl std::fmt::Display for ModuleId {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 write!(f, "{}", self.0)
42 }
43}
44
45#[derive(Debug, Clone, Default)]
46pub(crate) struct ModuleLoader {
47 pub import_stack: Vec<TypedPath>,
50}
51
52impl ModuleLoader {
53 pub(crate) fn cycle_check(&self, path: &ModulePath, source_range: SourceRange) -> Result<(), KclError> {
54 if self.import_stack.contains(path.expect_path()) {
55 return Err(self.import_cycle_error(path, source_range));
56 }
57 Ok(())
58 }
59
60 pub(crate) fn import_cycle_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
61 KclError::new_import_cycle(KclErrorDetails::new(
62 format!(
63 "circular import of modules is not allowed: {} -> {}",
64 self.import_stack
65 .iter()
66 .map(|p| p.to_string_lossy())
67 .collect::<Vec<_>>()
68 .join(" -> "),
69 path,
70 ),
71 vec![source_range],
72 ))
73 }
74
75 pub(crate) fn enter_module(&mut self, path: &ModulePath) {
76 if let ModulePath::Local { value: path } = path {
77 self.import_stack.push(path.clone());
78 }
79 }
80
81 pub(crate) fn leave_module(&mut self, path: &ModulePath) {
82 if let ModulePath::Local { value: path } = path {
83 let popped = self.import_stack.pop().unwrap();
84 assert_eq!(path, &popped);
85 }
86 }
87}
88
89pub(crate) fn read_std(mod_name: &str) -> Option<&'static str> {
90 match mod_name {
91 "prelude" => Some(include_str!("../std/prelude.kcl")),
92 "math" => Some(include_str!("../std/math.kcl")),
93 "sketch" => Some(include_str!("../std/sketch.kcl")),
94 "turns" => Some(include_str!("../std/turns.kcl")),
95 "types" => Some(include_str!("../std/types.kcl")),
96 "solid" => Some(include_str!("../std/solid.kcl")),
97 "units" => Some(include_str!("../std/units.kcl")),
98 "array" => Some(include_str!("../std/array.kcl")),
99 "sweep" => Some(include_str!("../std/sweep.kcl")),
100 "appearance" => Some(include_str!("../std/appearance.kcl")),
101 "transform" => Some(include_str!("../std/transform.kcl")),
102 _ => None,
103 }
104}
105
106#[derive(Debug, Clone, PartialEq, Serialize)]
108pub struct ModuleInfo {
109 pub(crate) id: ModuleId,
111 pub(crate) path: ModulePath,
113 pub(crate) repr: ModuleRepr,
114}
115
116impl ModuleInfo {
117 pub(crate) fn take_repr(&mut self) -> ModuleRepr {
118 let mut result = ModuleRepr::Dummy;
119 std::mem::swap(&mut self.repr, &mut result);
120 result
121 }
122
123 pub(crate) fn restore_repr(&mut self, repr: ModuleRepr) {
124 assert!(matches!(&self.repr, ModuleRepr::Dummy));
125 self.repr = repr;
126 }
127}
128
129#[allow(clippy::large_enum_variant)]
130#[derive(Debug, Clone, PartialEq, Serialize)]
131pub enum ModuleRepr {
132 Root,
133 Kcl(
135 Node<Program>,
136 Option<(Option<KclValue>, EnvironmentRef, Vec<String>, ModuleArtifactState)>,
137 ),
138 Foreign(PreImportedGeometry, Option<(Option<KclValue>, ModuleArtifactState)>),
139 Dummy,
140}
141
142#[allow(clippy::large_enum_variant)]
143#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Hash, ts_rs::TS)]
144#[serde(tag = "type")]
145pub enum ModulePath {
146 Main,
148 Local { value: TypedPath },
149 Std { value: String },
150}
151
152impl ModulePath {
153 pub(crate) fn expect_path(&self) -> &TypedPath {
154 match self {
155 ModulePath::Local { value: p } => p,
156 _ => unreachable!(),
157 }
158 }
159
160 pub(crate) async fn source(&self, fs: &FileManager, source_range: SourceRange) -> Result<ModuleSource, KclError> {
161 match self {
162 ModulePath::Local { value: p } => Ok(ModuleSource {
163 source: fs.read_to_string(p, source_range).await?,
164 path: self.clone(),
165 }),
166 ModulePath::Std { value: name } => Ok(ModuleSource {
167 source: read_std(name)
168 .ok_or_else(|| {
169 KclError::new_semantic(KclErrorDetails::new(
170 format!("Cannot find standard library module to import: std::{name}."),
171 vec![source_range],
172 ))
173 })
174 .map(str::to_owned)?,
175 path: self.clone(),
176 }),
177 ModulePath::Main => unreachable!(),
178 }
179 }
180
181 pub(crate) fn from_import_path(
182 path: &ImportPath,
183 project_directory: &Option<TypedPath>,
184 import_from: &ModulePath,
185 ) -> Result<Self, KclError> {
186 match path {
187 ImportPath::Kcl { filename: path } | ImportPath::Foreign { path } => {
188 let resolved_path = match import_from {
189 ModulePath::Main => {
190 if let Some(project_dir) = project_directory {
191 project_dir.join_typed(path)
192 } else {
193 path.clone()
194 }
195 }
196 ModulePath::Local { value } => {
197 let import_from_dir = value.parent();
198 let base = import_from_dir.as_ref().or(project_directory.as_ref());
199 if let Some(dir) = base {
200 dir.join_typed(path)
201 } else {
202 path.clone()
203 }
204 }
205 ModulePath::Std { .. } => {
206 let message = format!("Cannot import a non-std KCL file from std: {path}.");
207 debug_assert!(false, "{}", &message);
208 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![])));
209 }
210 };
211
212 Ok(ModulePath::Local { value: resolved_path })
213 }
214 ImportPath::Std { path } => Self::from_std_import_path(path),
215 }
216 }
217
218 pub(crate) fn from_std_import_path(path: &[String]) -> Result<Self, KclError> {
219 if path.len() != 2 || path[0] != "std" {
221 let message = format!("Invalid std import path: {path:?}.");
222 debug_assert!(false, "{}", &message);
223 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![])));
224 }
225
226 Ok(ModulePath::Std { value: path[1].clone() })
227 }
228}
229
230impl fmt::Display for ModulePath {
231 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232 match self {
233 ModulePath::Main => write!(f, "main"),
234 ModulePath::Local { value: path } => path.fmt(f),
235 ModulePath::Std { value: s } => write!(f, "std::{s}"),
236 }
237 }
238}
239
240#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, ts_rs::TS)]
241pub struct ModuleSource {
242 pub path: ModulePath,
243 pub source: String,
244}