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_all_old_sketches_to_new, transpile_old_sketch_to_new, transpile_old_sketch_to_new_ast,
99 transpile_old_sketch_to_new_with_execution, typed_path::TypedPath,
100};
101pub use kcl_error::SourceRange;
102pub use lsp::{
103 ToLspRange,
104 copilot::Backend as CopilotLspBackend,
105 kcl::{Backend as KclLspBackend, Server as KclLspServerSubCommand},
106};
107pub use modules::ModuleId;
108pub use parsing::ast::types::{FormatOptions, NodePath, Step as NodePathStep};
109pub use project::ProjectManager;
110pub use settings::types::{Configuration, project::ProjectConfiguration};
111#[cfg(not(target_arch = "wasm32"))]
112pub use unparser::{recast_dir, walk_dir};
113
114pub mod exec {
117 #[cfg(feature = "artifact-graph")]
118 pub use crate::execution::{ArtifactCommand, Operation};
119 pub use crate::execution::{
120 DefaultPlanes, IdGenerator, KclValue, PlaneKind, Sketch,
121 annotations::WarningLevel,
122 types::{NumericType, UnitType},
123 };
124}
125
126#[cfg(target_arch = "wasm32")]
127pub mod wasm_engine {
128 pub use crate::{
129 coredump::wasm::{CoreDumpManager, CoreDumper},
130 engine::conn_wasm::{EngineCommandManager, EngineConnection, ResponseContext},
131 fs::wasm::{FileManager, FileSystemManager},
132 };
133}
134
135pub mod mock_engine {
136 pub use crate::engine::conn_mock::EngineConnection;
137}
138
139#[cfg(not(target_arch = "wasm32"))]
140pub mod native_engine {
141 pub use crate::engine::conn::EngineConnection;
142}
143
144pub mod std_utils {
145 pub use crate::std::utils::{
146 TangentialArcInfoInput, get_tangential_arc_to_info, is_points_ccw_wasm, untyped_point_to_unit,
147 };
148}
149
150pub mod pretty {
151 pub use crate::{
152 fmt::{format_number_literal, format_number_value, human_display_number},
153 parsing::token::NumericSuffix,
154 };
155}
156
157pub mod front {
158 pub(crate) use crate::frontend::modify::{find_defined_names, next_free_name_using_max};
159 pub use crate::frontend::{
161 FrontendState, SetProgramOutcome,
162 api::{
163 Error, Expr, Face, File, FileId, LifecycleApi, Number, Object, ObjectId, ObjectKind, Plane, ProjectId,
164 Result, SceneGraph, SceneGraphDelta, Settings, SourceDelta, SourceRef, Version,
165 },
166 sketch::{
167 Angle, Arc, ArcCtor, Circle, CircleCtor, Coincident, Constraint, Distance, ExistingSegmentCtor, Freedom,
168 Horizontal, Line, LineCtor, LinesEqualLength, NewSegmentInfo, Parallel, Perpendicular, Point, Point2d,
169 PointCtor, Segment, SegmentCtor, Sketch, SketchApi, SketchCtor, StartOrEnd, Tangent, TangentArcCtor,
170 Vertical,
171 },
172 trim::{
173 ArcPoint, AttachToEndpoint, CoincidentData, ConstraintToMigrate, Coords2d, EndpointChanged, LineEndpoint,
174 TrimDirection, TrimItem, TrimOperation, TrimTermination, TrimTerminations, arc_arc_intersection,
175 execute_trim_loop_with_context, get_next_trim_spawn, get_position_coords_for_line,
176 get_position_coords_from_arc, get_trim_spawn_terminations, is_point_on_arc, is_point_on_line_segment,
177 line_arc_intersection, line_segment_intersection, perpendicular_distance_to_segment,
178 project_point_onto_arc, project_point_onto_segment,
179 },
180 };
181}
182
183#[cfg(feature = "cli")]
184use clap::ValueEnum;
185use serde::{Deserialize, Serialize};
186
187use crate::exec::WarningLevel;
188#[allow(unused_imports)]
189use crate::log::{log, logln};
190
191lazy_static::lazy_static! {
192
193 pub static ref IMPORT_FILE_EXTENSIONS: Vec<String> = {
194 let mut import_file_extensions = vec!["stp".to_string(), "glb".to_string(), "fbxb".to_string()];
195 #[cfg(feature = "cli")]
196 let named_extensions = kittycad::types::FileImportFormat::value_variants()
197 .iter()
198 .map(|x| format!("{x}"))
199 .collect::<Vec<String>>();
200 #[cfg(not(feature = "cli"))]
201 let named_extensions = vec![]; import_file_extensions.extend_from_slice(&named_extensions);
204 import_file_extensions
205 };
206
207 pub static ref RELEVANT_FILE_EXTENSIONS: Vec<String> = {
208 let mut relevant_extensions = IMPORT_FILE_EXTENSIONS.clone();
209 relevant_extensions.push("kcl".to_string());
210 relevant_extensions
211 };
212}
213
214#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
215pub struct Program {
216 #[serde(flatten)]
217 pub ast: parsing::ast::types::Node<parsing::ast::types::Program>,
218 #[serde(skip)]
222 pub original_file_contents: String,
223}
224
225#[cfg(any(test, feature = "lsp-test-util"))]
226pub use lsp::test_util::copilot_lsp_server;
227#[cfg(any(test, feature = "lsp-test-util"))]
228pub use lsp::test_util::kcl_lsp_server;
229
230impl Program {
231 pub fn parse(input: &str) -> Result<(Option<Program>, Vec<CompilationError>), KclError> {
232 let module_id = ModuleId::default();
233 let (ast, errs) = parsing::parse_str(input, module_id).0?;
234
235 Ok((
236 ast.map(|ast| Program {
237 ast,
238 original_file_contents: input.to_string(),
239 }),
240 errs,
241 ))
242 }
243
244 pub fn parse_no_errs(input: &str) -> Result<Program, KclError> {
245 let module_id = ModuleId::default();
246 let ast = parsing::parse_str(input, module_id).parse_errs_as_err()?;
247
248 Ok(Program {
249 ast,
250 original_file_contents: input.to_string(),
251 })
252 }
253
254 pub fn compute_digest(&mut self) -> parsing::ast::digest::Digest {
255 self.ast.compute_digest()
256 }
257
258 pub fn meta_settings(&self) -> Result<Option<crate::MetaSettings>, KclError> {
260 self.ast.meta_settings()
261 }
262
263 pub fn change_default_units(
265 &self,
266 length_units: Option<kittycad_modeling_cmds::units::UnitLength>,
267 ) -> Result<Self, KclError> {
268 Ok(Self {
269 ast: self.ast.change_default_units(length_units)?,
270 original_file_contents: self.original_file_contents.clone(),
271 })
272 }
273
274 pub fn change_experimental_features(&self, warning_level: Option<WarningLevel>) -> Result<Self, KclError> {
275 Ok(Self {
276 ast: self.ast.change_experimental_features(warning_level)?,
277 original_file_contents: self.original_file_contents.clone(),
278 })
279 }
280
281 pub fn is_empty_or_only_settings(&self) -> bool {
282 self.ast.is_empty_or_only_settings()
283 }
284
285 pub fn lint_all(&self) -> Result<Vec<lint::Discovered>, anyhow::Error> {
286 self.ast.lint_all()
287 }
288
289 pub fn lint<'a>(&'a self, rule: impl lint::Rule<'a>) -> Result<Vec<lint::Discovered>, anyhow::Error> {
290 self.ast.lint(rule)
291 }
292
293 #[cfg(feature = "artifact-graph")]
294 pub fn node_path_from_range(&self, cached_body_items: usize, range: SourceRange) -> Option<NodePath> {
295 let module_infos = indexmap::IndexMap::new();
296 let programs = crate::execution::ProgramLookup::new(self.ast.clone(), module_infos);
297 NodePath::from_range(&programs, cached_body_items, range)
298 }
299
300 pub fn recast(&self) -> String {
301 self.ast.recast_top(&Default::default(), 0)
303 }
304
305 pub fn recast_with_options(&self, options: &FormatOptions) -> String {
306 self.ast.recast_top(options, 0)
307 }
308
309 pub fn empty() -> Self {
311 Self {
312 ast: parsing::ast::types::Node::no_src(parsing::ast::types::Program::default()),
313 original_file_contents: String::new(),
314 }
315 }
316}
317
318#[inline]
319fn try_f64_to_usize(f: f64) -> Option<usize> {
320 let i = f as usize;
321 if i as f64 == f { Some(i) } else { None }
322}
323
324#[inline]
325fn try_f64_to_u32(f: f64) -> Option<u32> {
326 let i = f as u32;
327 if i as f64 == f { Some(i) } else { None }
328}
329
330#[inline]
331fn try_f64_to_u64(f: f64) -> Option<u64> {
332 let i = f as u64;
333 if i as f64 == f { Some(i) } else { None }
334}
335
336#[inline]
337fn try_f64_to_i64(f: f64) -> Option<i64> {
338 let i = f as i64;
339 if i as f64 == f { Some(i) } else { None }
340}
341
342pub fn version() -> &'static str {
344 env!("CARGO_PKG_VERSION")
345}
346
347#[cfg(test)]
348mod test {
349 use super::*;
350
351 #[test]
352 fn convert_int() {
353 assert_eq!(try_f64_to_usize(0.0), Some(0));
354 assert_eq!(try_f64_to_usize(42.0), Some(42));
355 assert_eq!(try_f64_to_usize(0.00000000001), None);
356 assert_eq!(try_f64_to_usize(-1.0), None);
357 assert_eq!(try_f64_to_usize(f64::NAN), None);
358 assert_eq!(try_f64_to_usize(f64::INFINITY), None);
359 assert_eq!(try_f64_to_usize((0.1 + 0.2) * 10.0), None);
360
361 assert_eq!(try_f64_to_u32(0.0), Some(0));
362 assert_eq!(try_f64_to_u32(42.0), Some(42));
363 assert_eq!(try_f64_to_u32(0.00000000001), None);
364 assert_eq!(try_f64_to_u32(-1.0), None);
365 assert_eq!(try_f64_to_u32(f64::NAN), None);
366 assert_eq!(try_f64_to_u32(f64::INFINITY), None);
367 assert_eq!(try_f64_to_u32((0.1 + 0.2) * 10.0), None);
368
369 assert_eq!(try_f64_to_u64(0.0), Some(0));
370 assert_eq!(try_f64_to_u64(42.0), Some(42));
371 assert_eq!(try_f64_to_u64(0.00000000001), None);
372 assert_eq!(try_f64_to_u64(-1.0), None);
373 assert_eq!(try_f64_to_u64(f64::NAN), None);
374 assert_eq!(try_f64_to_u64(f64::INFINITY), None);
375 assert_eq!(try_f64_to_u64((0.1 + 0.2) * 10.0), None);
376
377 assert_eq!(try_f64_to_i64(0.0), Some(0));
378 assert_eq!(try_f64_to_i64(42.0), Some(42));
379 assert_eq!(try_f64_to_i64(0.00000000001), None);
380 assert_eq!(try_f64_to_i64(-1.0), Some(-1));
381 assert_eq!(try_f64_to_i64(f64::NAN), None);
382 assert_eq!(try_f64_to_i64(f64::INFINITY), None);
383 assert_eq!(try_f64_to_i64((0.1 + 0.2) * 10.0), None);
384 }
385}