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, transpile_old_sketch_to_new, transpile_old_sketch_to_new_with_execution, 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, PlaneKind, 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(crate) use crate::frontend::modify::{find_defined_names, next_free_name_using_max};
158 pub use crate::frontend::{
160 FrontendState, SetProgramOutcome,
161 api::{
162 Error, Expr, Face, File, FileId, LifecycleApi, Number, Object, ObjectId, ObjectKind, Plane, ProjectId,
163 Result, SceneGraph, SceneGraphDelta, Settings, SourceDelta, SourceRef, Version,
164 },
165 sketch::{
166 Arc, ArcCtor, Circle, CircleCtor, Coincident, Constraint, Distance, ExistingSegmentCtor, Freedom,
167 Horizontal, Line, LineCtor, LinesEqualLength, NewSegmentInfo, Parallel, Perpendicular, Point, Point2d,
168 PointCtor, Segment, SegmentCtor, Sketch, SketchApi, SketchCtor, StartOrEnd, TangentArcCtor, Vertical,
169 },
170 trim::{
171 ArcPoint, AttachToEndpoint, CoincidentData, ConstraintToMigrate, Coords2d, EndpointChanged, LineEndpoint,
172 TrimDirection, TrimItem, TrimOperation, TrimTermination, TrimTerminations, arc_arc_intersection,
173 execute_trim_loop_with_context, get_next_trim_spawn, get_position_coords_for_line,
174 get_position_coords_from_arc, get_trim_spawn_terminations, is_point_on_arc, is_point_on_line_segment,
175 line_arc_intersection, line_segment_intersection, perpendicular_distance_to_segment,
176 project_point_onto_arc, project_point_onto_segment,
177 },
178 };
179}
180
181#[cfg(feature = "cli")]
182use clap::ValueEnum;
183use serde::{Deserialize, Serialize};
184
185use crate::exec::WarningLevel;
186#[allow(unused_imports)]
187use crate::log::{log, logln};
188
189lazy_static::lazy_static! {
190
191 pub static ref IMPORT_FILE_EXTENSIONS: Vec<String> = {
192 let mut import_file_extensions = vec!["stp".to_string(), "glb".to_string(), "fbxb".to_string()];
193 #[cfg(feature = "cli")]
194 let named_extensions = kittycad::types::FileImportFormat::value_variants()
195 .iter()
196 .map(|x| format!("{x}"))
197 .collect::<Vec<String>>();
198 #[cfg(not(feature = "cli"))]
199 let named_extensions = vec![]; import_file_extensions.extend_from_slice(&named_extensions);
202 import_file_extensions
203 };
204
205 pub static ref RELEVANT_FILE_EXTENSIONS: Vec<String> = {
206 let mut relevant_extensions = IMPORT_FILE_EXTENSIONS.clone();
207 relevant_extensions.push("kcl".to_string());
208 relevant_extensions
209 };
210}
211
212#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
213pub struct Program {
214 #[serde(flatten)]
215 pub ast: parsing::ast::types::Node<parsing::ast::types::Program>,
216 #[serde(skip)]
220 pub original_file_contents: String,
221}
222
223#[cfg(any(test, feature = "lsp-test-util"))]
224pub use lsp::test_util::copilot_lsp_server;
225#[cfg(any(test, feature = "lsp-test-util"))]
226pub use lsp::test_util::kcl_lsp_server;
227
228impl Program {
229 pub fn parse(input: &str) -> Result<(Option<Program>, Vec<CompilationError>), KclError> {
230 let module_id = ModuleId::default();
231 let (ast, errs) = parsing::parse_str(input, module_id).0?;
232
233 Ok((
234 ast.map(|ast| Program {
235 ast,
236 original_file_contents: input.to_string(),
237 }),
238 errs,
239 ))
240 }
241
242 pub fn parse_no_errs(input: &str) -> Result<Program, KclError> {
243 let module_id = ModuleId::default();
244 let ast = parsing::parse_str(input, module_id).parse_errs_as_err()?;
245
246 Ok(Program {
247 ast,
248 original_file_contents: input.to_string(),
249 })
250 }
251
252 pub fn compute_digest(&mut self) -> parsing::ast::digest::Digest {
253 self.ast.compute_digest()
254 }
255
256 pub fn meta_settings(&self) -> Result<Option<crate::MetaSettings>, KclError> {
258 self.ast.meta_settings()
259 }
260
261 pub fn change_default_units(
263 &self,
264 length_units: Option<kittycad_modeling_cmds::units::UnitLength>,
265 ) -> Result<Self, KclError> {
266 Ok(Self {
267 ast: self.ast.change_default_units(length_units)?,
268 original_file_contents: self.original_file_contents.clone(),
269 })
270 }
271
272 pub fn change_experimental_features(&self, warning_level: Option<WarningLevel>) -> Result<Self, KclError> {
273 Ok(Self {
274 ast: self.ast.change_experimental_features(warning_level)?,
275 original_file_contents: self.original_file_contents.clone(),
276 })
277 }
278
279 pub fn is_empty_or_only_settings(&self) -> bool {
280 self.ast.is_empty_or_only_settings()
281 }
282
283 pub fn lint_all(&self) -> Result<Vec<lint::Discovered>, anyhow::Error> {
284 self.ast.lint_all()
285 }
286
287 pub fn lint<'a>(&'a self, rule: impl lint::Rule<'a>) -> Result<Vec<lint::Discovered>, anyhow::Error> {
288 self.ast.lint(rule)
289 }
290
291 #[cfg(feature = "artifact-graph")]
292 pub fn node_path_from_range(&self, cached_body_items: usize, range: SourceRange) -> Option<NodePath> {
293 let module_infos = indexmap::IndexMap::new();
294 let programs = crate::execution::ProgramLookup::new(self.ast.clone(), module_infos);
295 NodePath::from_range(&programs, cached_body_items, range)
296 }
297
298 pub fn recast(&self) -> String {
299 self.ast.recast_top(&Default::default(), 0)
301 }
302
303 pub fn recast_with_options(&self, options: &FormatOptions) -> String {
304 self.ast.recast_top(options, 0)
305 }
306
307 pub fn empty() -> Self {
309 Self {
310 ast: parsing::ast::types::Node::no_src(parsing::ast::types::Program::default()),
311 original_file_contents: String::new(),
312 }
313 }
314}
315
316#[inline]
317fn try_f64_to_usize(f: f64) -> Option<usize> {
318 let i = f as usize;
319 if i as f64 == f { Some(i) } else { None }
320}
321
322#[inline]
323fn try_f64_to_u32(f: f64) -> Option<u32> {
324 let i = f as u32;
325 if i as f64 == f { Some(i) } else { None }
326}
327
328#[inline]
329fn try_f64_to_u64(f: f64) -> Option<u64> {
330 let i = f as u64;
331 if i as f64 == f { Some(i) } else { None }
332}
333
334#[inline]
335fn try_f64_to_i64(f: f64) -> Option<i64> {
336 let i = f as i64;
337 if i as f64 == f { Some(i) } else { None }
338}
339
340pub fn version() -> &'static str {
342 env!("CARGO_PKG_VERSION")
343}
344
345#[cfg(test)]
346mod test {
347 use super::*;
348
349 #[test]
350 fn convert_int() {
351 assert_eq!(try_f64_to_usize(0.0), Some(0));
352 assert_eq!(try_f64_to_usize(42.0), Some(42));
353 assert_eq!(try_f64_to_usize(0.00000000001), None);
354 assert_eq!(try_f64_to_usize(-1.0), None);
355 assert_eq!(try_f64_to_usize(f64::NAN), None);
356 assert_eq!(try_f64_to_usize(f64::INFINITY), None);
357 assert_eq!(try_f64_to_usize((0.1 + 0.2) * 10.0), None);
358
359 assert_eq!(try_f64_to_u32(0.0), Some(0));
360 assert_eq!(try_f64_to_u32(42.0), Some(42));
361 assert_eq!(try_f64_to_u32(0.00000000001), None);
362 assert_eq!(try_f64_to_u32(-1.0), None);
363 assert_eq!(try_f64_to_u32(f64::NAN), None);
364 assert_eq!(try_f64_to_u32(f64::INFINITY), None);
365 assert_eq!(try_f64_to_u32((0.1 + 0.2) * 10.0), None);
366
367 assert_eq!(try_f64_to_u64(0.0), Some(0));
368 assert_eq!(try_f64_to_u64(42.0), Some(42));
369 assert_eq!(try_f64_to_u64(0.00000000001), None);
370 assert_eq!(try_f64_to_u64(-1.0), None);
371 assert_eq!(try_f64_to_u64(f64::NAN), None);
372 assert_eq!(try_f64_to_u64(f64::INFINITY), None);
373 assert_eq!(try_f64_to_u64((0.1 + 0.2) * 10.0), None);
374
375 assert_eq!(try_f64_to_i64(0.0), Some(0));
376 assert_eq!(try_f64_to_i64(42.0), Some(42));
377 assert_eq!(try_f64_to_i64(0.00000000001), None);
378 assert_eq!(try_f64_to_i64(-1.0), Some(-1));
379 assert_eq!(try_f64_to_i64(f64::NAN), None);
380 assert_eq!(try_f64_to_i64(f64::INFINITY), None);
381 assert_eq!(try_f64_to_i64((0.1 + 0.2) * 10.0), None);
382 }
383}