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