1#![recursion_limit = "1024"]
6#![allow(clippy::boxed_local)]
7
8#[allow(unused_macros)]
9macro_rules! println {
10 ($($rest:tt)*) => {
11 #[cfg(all(feature = "disable-println", not(test)))]
12 {
13 let _ = format!($($rest)*);
14 }
15 #[cfg(any(not(feature = "disable-println"), test))]
16 std::println!($($rest)*)
17 }
18}
19
20#[allow(unused_macros)]
21macro_rules! eprintln {
22 ($($rest:tt)*) => {
23 #[cfg(all(feature = "disable-println", not(test)))]
24 {
25 let _ = format!($($rest)*);
26 }
27 #[cfg(any(not(feature = "disable-println"), test))]
28 std::eprintln!($($rest)*)
29 }
30}
31
32#[allow(unused_macros)]
33macro_rules! print {
34 ($($rest:tt)*) => {
35 #[cfg(all(feature = "disable-println", not(test)))]
36 {
37 let _ = format!($($rest)*);
38 }
39 #[cfg(any(not(feature = "disable-println"), test))]
40 std::print!($($rest)*)
41 }
42}
43
44#[allow(unused_macros)]
45macro_rules! eprint {
46 ($($rest:tt)*) => {
47 #[cfg(all(feature = "disable-println", not(test)))]
48 {
49 let _ = format!($($rest)*);
50 }
51 #[cfg(any(not(feature = "disable-println"), test))]
52 std::eprint!($($rest)*)
53 }
54}
55#[cfg(feature = "dhat-heap")]
56#[global_allocator]
57static ALLOC: dhat::Alloc = dhat::Alloc;
58
59pub mod collections;
60mod coredump;
61mod docs;
62mod engine;
63mod errors;
64mod execution;
65mod fmt;
66mod frontend;
67mod fs;
68#[cfg(feature = "artifact-graph")]
69pub(crate) mod id;
70pub mod lint;
71mod log;
72mod lsp;
73mod modules;
74mod parsing;
75mod project;
76mod settings;
77#[cfg(test)]
78mod simulation_tests;
79pub mod std;
80#[cfg(not(target_arch = "wasm32"))]
81pub mod test_server;
82mod thread;
83mod unparser;
84#[cfg(test)]
85mod variant_name;
86pub mod walk;
87#[cfg(target_arch = "wasm32")]
88mod wasm;
89
90pub use coredump::CoreDump;
91pub use engine::{AsyncTasks, EngineManager, EngineStats};
92pub use errors::{
93 BacktraceItem, CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs, Report,
94 ReportWithOutputs,
95};
96pub use execution::{
97 ExecOutcome, ExecState, ExecutorContext, ExecutorSettings, MetaSettings, MockConfig, Point2d, bust_cache,
98 clear_mem_cache, typed_path::TypedPath,
99};
100pub use kcl_error::SourceRange;
101pub use lsp::{
102 ToLspRange,
103 copilot::Backend as CopilotLspBackend,
104 kcl::{Backend as KclLspBackend, Server as KclLspServerSubCommand},
105};
106pub use modules::ModuleId;
107pub use parsing::ast::types::{FormatOptions, NodePath, Step as NodePathStep};
108pub use project::ProjectManager;
109pub use settings::types::{Configuration, project::ProjectConfiguration};
110#[cfg(not(target_arch = "wasm32"))]
111pub use unparser::{recast_dir, walk_dir};
112
113pub mod exec {
116 #[cfg(feature = "artifact-graph")]
117 pub use crate::execution::{ArtifactCommand, Operation};
118 pub use crate::execution::{
119 DefaultPlanes, IdGenerator, KclValue, PlaneType, Sketch,
120 annotations::WarningLevel,
121 types::{NumericType, UnitType},
122 };
123}
124
125#[cfg(target_arch = "wasm32")]
126pub mod wasm_engine {
127 pub use crate::{
128 coredump::wasm::{CoreDumpManager, CoreDumper},
129 engine::conn_wasm::{EngineCommandManager, EngineConnection, ResponseContext},
130 fs::wasm::{FileManager, FileSystemManager},
131 };
132}
133
134pub mod mock_engine {
135 pub use crate::engine::conn_mock::EngineConnection;
136}
137
138#[cfg(not(target_arch = "wasm32"))]
139pub mod native_engine {
140 pub use crate::engine::conn::EngineConnection;
141}
142
143pub mod std_utils {
144 pub use crate::std::utils::{
145 TangentialArcInfoInput, get_tangential_arc_to_info, is_points_ccw_wasm, untyped_point_to_unit,
146 };
147}
148
149pub mod pretty {
150 pub use crate::{
151 fmt::{format_number_literal, format_number_value, human_display_number},
152 parsing::token::NumericSuffix,
153 };
154}
155
156pub mod front {
157 pub use crate::frontend::{
158 FrontendState,
159 api::{
160 Error, Expr, File, FileId, LifecycleApi, Number, Object, ObjectId, ObjectKind, Plane, ProjectId, Result,
161 SceneGraph, SceneGraphDelta, Settings, SourceDelta, SourceRef, Version,
162 },
163 sketch::{
164 Arc, ArcCtor, Circle, CircleCtor, Coincident, Constraint, Distance, ExistingSegmentCtor, Freedom,
165 Horizontal, Line, LineCtor, LinesEqualLength, Parallel, Point, Point2d, PointCtor, Segment, SegmentCtor,
166 Sketch, SketchApi, SketchArgs, StartOrEnd, TangentArcCtor, Vertical,
167 },
168 };
169}
170
171#[cfg(feature = "cli")]
172use clap::ValueEnum;
173use serde::{Deserialize, Serialize};
174
175use crate::exec::WarningLevel;
176#[allow(unused_imports)]
177use crate::log::{log, logln};
178
179lazy_static::lazy_static! {
180
181 pub static ref IMPORT_FILE_EXTENSIONS: Vec<String> = {
182 let mut import_file_extensions = vec!["stp".to_string(), "glb".to_string(), "fbxb".to_string()];
183 #[cfg(feature = "cli")]
184 let named_extensions = kittycad::types::FileImportFormat::value_variants()
185 .iter()
186 .map(|x| format!("{x}"))
187 .collect::<Vec<String>>();
188 #[cfg(not(feature = "cli"))]
189 let named_extensions = vec![]; import_file_extensions.extend_from_slice(&named_extensions);
192 import_file_extensions
193 };
194
195 pub static ref RELEVANT_FILE_EXTENSIONS: Vec<String> = {
196 let mut relevant_extensions = IMPORT_FILE_EXTENSIONS.clone();
197 relevant_extensions.push("kcl".to_string());
198 relevant_extensions
199 };
200}
201
202#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
203pub struct Program {
204 #[serde(flatten)]
205 pub ast: parsing::ast::types::Node<parsing::ast::types::Program>,
206 #[serde(skip)]
210 pub original_file_contents: String,
211}
212
213#[cfg(any(test, feature = "lsp-test-util"))]
214pub use lsp::test_util::copilot_lsp_server;
215#[cfg(any(test, feature = "lsp-test-util"))]
216pub use lsp::test_util::kcl_lsp_server;
217
218impl Program {
219 pub fn parse(input: &str) -> Result<(Option<Program>, Vec<CompilationError>), KclError> {
220 let module_id = ModuleId::default();
221 let (ast, errs) = parsing::parse_str(input, module_id).0?;
222
223 Ok((
224 ast.map(|ast| Program {
225 ast,
226 original_file_contents: input.to_string(),
227 }),
228 errs,
229 ))
230 }
231
232 pub fn parse_no_errs(input: &str) -> Result<Program, KclError> {
233 let module_id = ModuleId::default();
234 let ast = parsing::parse_str(input, module_id).parse_errs_as_err()?;
235
236 Ok(Program {
237 ast,
238 original_file_contents: input.to_string(),
239 })
240 }
241
242 pub fn compute_digest(&mut self) -> parsing::ast::digest::Digest {
243 self.ast.compute_digest()
244 }
245
246 pub fn meta_settings(&self) -> Result<Option<crate::MetaSettings>, KclError> {
248 self.ast.meta_settings()
249 }
250
251 pub fn change_default_units(
253 &self,
254 length_units: Option<kittycad_modeling_cmds::units::UnitLength>,
255 ) -> Result<Self, KclError> {
256 Ok(Self {
257 ast: self.ast.change_default_units(length_units)?,
258 original_file_contents: self.original_file_contents.clone(),
259 })
260 }
261
262 pub fn change_experimental_features(&self, warning_level: Option<WarningLevel>) -> Result<Self, KclError> {
263 Ok(Self {
264 ast: self.ast.change_experimental_features(warning_level)?,
265 original_file_contents: self.original_file_contents.clone(),
266 })
267 }
268
269 pub fn is_empty_or_only_settings(&self) -> bool {
270 self.ast.is_empty_or_only_settings()
271 }
272
273 pub fn lint_all(&self) -> Result<Vec<lint::Discovered>, anyhow::Error> {
274 self.ast.lint_all()
275 }
276
277 pub fn lint<'a>(&'a self, rule: impl lint::Rule<'a>) -> Result<Vec<lint::Discovered>, anyhow::Error> {
278 self.ast.lint(rule)
279 }
280
281 #[cfg(feature = "artifact-graph")]
282 pub fn node_path_from_range(&self, cached_body_items: usize, range: SourceRange) -> Option<NodePath> {
283 let module_infos = indexmap::IndexMap::new();
284 let programs = crate::execution::ProgramLookup::new(self.ast.clone(), module_infos);
285 NodePath::from_range(&programs, cached_body_items, range)
286 }
287
288 pub fn recast(&self) -> String {
289 self.ast.recast_top(&Default::default(), 0)
291 }
292
293 pub fn recast_with_options(&self, options: &FormatOptions) -> String {
294 self.ast.recast_top(options, 0)
295 }
296
297 pub fn empty() -> Self {
299 Self {
300 ast: parsing::ast::types::Node::no_src(parsing::ast::types::Program::default()),
301 original_file_contents: String::new(),
302 }
303 }
304}
305
306#[inline]
307fn try_f64_to_usize(f: f64) -> Option<usize> {
308 let i = f as usize;
309 if i as f64 == f { Some(i) } else { None }
310}
311
312#[inline]
313fn try_f64_to_u32(f: f64) -> Option<u32> {
314 let i = f as u32;
315 if i as f64 == f { Some(i) } else { None }
316}
317
318#[inline]
319fn try_f64_to_u64(f: f64) -> Option<u64> {
320 let i = f as u64;
321 if i as f64 == f { Some(i) } else { None }
322}
323
324#[inline]
325fn try_f64_to_i64(f: f64) -> Option<i64> {
326 let i = f as i64;
327 if i as f64 == f { Some(i) } else { None }
328}
329
330pub fn version() -> &'static str {
332 env!("CARGO_PKG_VERSION")
333}
334
335#[cfg(test)]
336mod test {
337 use super::*;
338
339 #[test]
340 fn convert_int() {
341 assert_eq!(try_f64_to_usize(0.0), Some(0));
342 assert_eq!(try_f64_to_usize(42.0), Some(42));
343 assert_eq!(try_f64_to_usize(0.00000000001), None);
344 assert_eq!(try_f64_to_usize(-1.0), None);
345 assert_eq!(try_f64_to_usize(f64::NAN), None);
346 assert_eq!(try_f64_to_usize(f64::INFINITY), None);
347 assert_eq!(try_f64_to_usize((0.1 + 0.2) * 10.0), None);
348
349 assert_eq!(try_f64_to_u32(0.0), Some(0));
350 assert_eq!(try_f64_to_u32(42.0), Some(42));
351 assert_eq!(try_f64_to_u32(0.00000000001), None);
352 assert_eq!(try_f64_to_u32(-1.0), None);
353 assert_eq!(try_f64_to_u32(f64::NAN), None);
354 assert_eq!(try_f64_to_u32(f64::INFINITY), None);
355 assert_eq!(try_f64_to_u32((0.1 + 0.2) * 10.0), None);
356
357 assert_eq!(try_f64_to_u64(0.0), Some(0));
358 assert_eq!(try_f64_to_u64(42.0), Some(42));
359 assert_eq!(try_f64_to_u64(0.00000000001), None);
360 assert_eq!(try_f64_to_u64(-1.0), None);
361 assert_eq!(try_f64_to_u64(f64::NAN), None);
362 assert_eq!(try_f64_to_u64(f64::INFINITY), None);
363 assert_eq!(try_f64_to_u64((0.1 + 0.2) * 10.0), None);
364
365 assert_eq!(try_f64_to_i64(0.0), Some(0));
366 assert_eq!(try_f64_to_i64(42.0), Some(42));
367 assert_eq!(try_f64_to_i64(0.00000000001), None);
368 assert_eq!(try_f64_to_i64(-1.0), Some(-1));
369 assert_eq!(try_f64_to_i64(f64::NAN), None);
370 assert_eq!(try_f64_to_i64(f64::INFINITY), None);
371 assert_eq!(try_f64_to_i64((0.1 + 0.2) * 10.0), None);
372 }
373}